Implemented phase 4 for the POC prototype in the Website service.
This commit is contained in:
1 parent
95e0a455e8
commit
9403931109
42 files changed
+3407
-36
No files matched your search
@@ -0,0 +1,126 @@
|
||||
/* CCN — grading guide. */
|
||||
|
||||
.guide,
|
||||
.guide__section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.guide {
|
||||
gap: var(--space-10);
|
||||
}
|
||||
|
||||
.guide__head {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
max-width: 760px;
|
||||
}
|
||||
|
||||
.guide__scale {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(72px, 1fr));
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.guide__grade {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
padding: var(--space-2) var(--space-1);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--card);
|
||||
color: var(--muted);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.guide__grade strong {
|
||||
color: var(--ink);
|
||||
font-family: var(--font-display);
|
||||
font-size: 18px;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.guide__section {
|
||||
gap: var(--space-5);
|
||||
}
|
||||
|
||||
.guide__intro {
|
||||
max-width: 720px;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
|
||||
.guide__references {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.guide__reference {
|
||||
display: grid;
|
||||
gap: var(--space-4);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.guide__reference-grade {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
.guide__photo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
aspect-ratio: 2 / 3;
|
||||
max-width: 140px;
|
||||
padding: var(--space-2);
|
||||
border: 1px dashed var(--rule);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--muted);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.08em;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.guide__reference .type-eyebrow {
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.guide__allowed {
|
||||
color: var(--good);
|
||||
}
|
||||
|
||||
.guide__excluded {
|
||||
color: var(--bad);
|
||||
}
|
||||
|
||||
.guide__defects {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.guide__defect {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
padding: var(--space-4);
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.guide {
|
||||
padding-top: var(--space-10);
|
||||
}
|
||||
|
||||
.guide__reference {
|
||||
grid-template-columns: 180px 140px minmax(0, 1fr) minmax(0, 1fr);
|
||||
gap: var(--space-6);
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,13 @@
|
||||
box-shadow: var(--shadow-cover);
|
||||
}
|
||||
|
||||
.listing__photos {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: var(--space-3);
|
||||
padding-top: var(--space-4);
|
||||
}
|
||||
|
||||
.listing__main {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
|
||||
@@ -1541,6 +1541,16 @@ textarea.input {
|
||||
padding-left: 16cqw;
|
||||
}
|
||||
|
||||
/* A photo of a copy, cropped to a cover's proportions. */
|
||||
.photo {
|
||||
display: block;
|
||||
width: 100%;
|
||||
aspect-ratio: 2 / 3;
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--tint);
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
/* Listing cards */
|
||||
.listing-card {
|
||||
position: relative;
|
||||
@@ -1747,6 +1757,13 @@ textarea.input {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.select--block {
|
||||
width: 100%;
|
||||
height: var(--control-md);
|
||||
border-color: var(--rule);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.sort {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -69,6 +69,12 @@
|
||||
gap: var(--space-7);
|
||||
}
|
||||
|
||||
.gate__demo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.gate__saved {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -782,3 +782,117 @@ a.rows__row:hover {
|
||||
grid-template-columns: 100px minmax(0, 2fr) 70px minmax(0, 1fr) 340px 130px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
New listing
|
||||
========================================================================== */
|
||||
|
||||
.listing-form,
|
||||
.listing-form__form,
|
||||
.listing-form__section,
|
||||
.listing-form__scope {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.listing-form {
|
||||
gap: var(--space-6);
|
||||
max-width: calc(880px + 2 * var(--gutter));
|
||||
}
|
||||
|
||||
.listing-form__head {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.listing-form__form {
|
||||
gap: var(--space-5);
|
||||
}
|
||||
|
||||
.listing-form__section {
|
||||
gap: var(--space-5);
|
||||
}
|
||||
|
||||
.listing-form__scope {
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.listing-form__grid {
|
||||
display: grid;
|
||||
gap: var(--space-4);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.listing-form__modes {
|
||||
display: grid;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.listing-form__mode {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-4);
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.listing-form__mode:has(input:checked) {
|
||||
border-color: var(--ink);
|
||||
background: var(--tint);
|
||||
}
|
||||
|
||||
.listing-form__mode span {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-1);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.listing-form__checks {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
gap: 10px var(--space-4);
|
||||
}
|
||||
|
||||
.listing-form__files {
|
||||
padding: var(--space-4);
|
||||
border: 1px dashed var(--rule);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--paper);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.listing-form__submit {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.sell__actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
@media (min-width: 600px) {
|
||||
.listing-form__grid {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.listing-form__modes {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.field--wide {
|
||||
grid-column: span 2;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.listing-form {
|
||||
padding-top: var(--space-8);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// CCN — passkey sign-in. Without it, or without WebAuthn, the sign-in screen offers the demo sign-in alone.
|
||||
|
||||
const form = document.querySelector("[data-passkey-form]");
|
||||
const supported = window.PublicKeyCredential
|
||||
&& PublicKeyCredential.parseCreationOptionsFromJSON
|
||||
&& PublicKeyCredential.parseRequestOptionsFromJSON;
|
||||
|
||||
if (form && supported) {
|
||||
const button = form.querySelector("[data-passkey]");
|
||||
const status = form.querySelector("[data-passkey-status]");
|
||||
|
||||
const post = async (path, body) => {
|
||||
const response = await fetch(path, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
const answer = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(answer.error ?? "Something went wrong. Try again.");
|
||||
}
|
||||
|
||||
return answer;
|
||||
};
|
||||
|
||||
button.hidden = false;
|
||||
button.addEventListener("click", async () => {
|
||||
button.disabled = true;
|
||||
status.textContent = "";
|
||||
|
||||
try {
|
||||
const { mode, options } = await post("/sign-in/passkey/options", { email: form.elements.email.value });
|
||||
let credential;
|
||||
|
||||
if (mode === "create") {
|
||||
// A discoverable passkey, so the next sign-in needs no email.
|
||||
options.authenticatorSelection = { residentKey: "required", userVerification: "preferred" };
|
||||
credential = await navigator.credentials.create({
|
||||
publicKey: PublicKeyCredential.parseCreationOptionsFromJSON(options),
|
||||
});
|
||||
} else {
|
||||
credential = await navigator.credentials.get({
|
||||
publicKey: PublicKeyCredential.parseRequestOptionsFromJSON(options),
|
||||
});
|
||||
}
|
||||
|
||||
const { location } = await post("/sign-in/passkey/finish", {
|
||||
credential: credential.toJSON(),
|
||||
return: form.elements.return.value,
|
||||
});
|
||||
|
||||
window.location.assign(location);
|
||||
} catch (error) {
|
||||
// Dismissing the browser's prompt is not an error worth a message.
|
||||
status.textContent = error.name === "NotAllowedError" ? "" : error.message;
|
||||
button.disabled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -21,3 +21,22 @@ if (filters) {
|
||||
sync();
|
||||
desktop.addEventListener("change", sync);
|
||||
}
|
||||
|
||||
// The new-listing form shows every field without scripts; here, only those that apply to the format and grading picked.
|
||||
const listingForm = document.querySelector(".listing-form__form");
|
||||
|
||||
if (listingForm) {
|
||||
const apply = () => {
|
||||
const format = listingForm.querySelector("input[name=format]:checked")?.value;
|
||||
const grading = listingForm.querySelector("input[name=grading]:checked")?.value;
|
||||
const single = format === "single-issues";
|
||||
const scopes = { "single-issues": single, collected: !single, slabbed: single && grading === "slabbed" };
|
||||
|
||||
for (const element of listingForm.querySelectorAll("[data-scope]")) {
|
||||
element.hidden = !scopes[element.dataset.scope];
|
||||
}
|
||||
};
|
||||
|
||||
listingForm.addEventListener("change", apply);
|
||||
apply();
|
||||
}
|
||||
Reference in new issue
Block a user