@@ -17,12 +17,13 @@ import { page, footer, appBar, esc } from "../lib/html.mjs";
1717import { requireAuth , csrfInput } from "../lib/session.mjs" ;
1818import { balance } from "../lib/credits.mjs" ;
1919import { resolverConfig } from "../lib/moshpit-resolvers.mjs" ;
20+ import { landingFor } from "../lib/moshpit-landing.mjs" ;
2021import {
2122 getTld , listTlds , listTldsForUser , registerTld , normalizeLabel ,
2223 setAlias , clearAlias , listExempt , setExempt , clearExempt ,
23- listNames , registerName , setNameTarget , releaseName ,
24+ listNames , getName , getTldWithPrice , registerName , setNameTarget , releaseName ,
2425 setTldPrice , listTldsNotOwnedBy , quoteName , openNamePurchase ,
25- resolveMoshpitName , normalizeTld , tldRejection ,
26+ resolveMoshpitName , normalizeTld , tldRejection , parseMoshpitName ,
2627 normalizeMode , resolutionPreference ,
2728} from "../moshpit.mjs" ;
2829import { config } from "../config.mjs" ;
@@ -256,14 +257,105 @@ moshpitRouter.get("/api/moshpit/resolve", async (req, res) => {
256257
257258/* ---------- the human page ---------- */
258259
259- const claimForm = ( req ) => `
260+ const claimForm = ( req , prefill = "" ) => `
260261<form method="post" action="/pit/claim" class="pit-form">
261262 ${ csrfInput ( req ) }
262263 <label class="pit-field"><span class="pit-dot">.</span
263- ><input name="tld" placeholder="eggs" aria-label="the TLD you want" autocomplete="off" spellcheck="false" required></label>
264+ ><input name="tld" placeholder="eggs" aria-label="the TLD you want" autocomplete="off" spellcheck="false"
265+ value="${ esc ( prefill ) } " required></label>
264266 <button class="btn acid" type="submit">Claim it</button>
265267</form>` ;
266268
269+ /**
270+ * The card someone lands on after typing `mosh.whatever` somewhere.
271+ *
272+ * A resolver or the gateway sent them here because the name did not resolve to
273+ * a site. They have just demonstrated demand for a name, so the page opens
274+ * with the shortest path from wanting it to holding it — and says plainly when
275+ * there is no such path, rather than inviting them into a flow that does not
276+ * exist.
277+ */
278+ const landingCard = ( req , landing ) => {
279+ if ( ! landing || landing . kind === "none" ) return "" ;
280+ const name = `<span class="mono acid">${ esc ( landing . name ) } </span>` ;
281+ const tld = `<span class="mono acid">.${ esc ( landing . tld ) } </span>` ;
282+
283+ if ( landing . kind === "claim-tld" ) {
284+ return `<div class="pit-land">
285+ <p class="label">you asked for ${ esc ( landing . name ) } </p>
286+ <h2>Nobody holds ${ tld } .</h2>
287+ <p class="pit-copy">Claim the ending and ${ name } — plus every other name under it — is yours to
288+ point wherever you like. First come, first served, and nobody can take it back.</p>
289+ ${ req . user ? claimForm ( req , landing . tld )
290+ : `<p class="pit-copy">Sign in with your moshcode account to claim it — the same login the CLI uses.</p>
291+ <p><a class="btn acid" href="/">Sign in →</a></p>` }
292+ </div>` ;
293+ }
294+
295+ if ( landing . kind === "mint-name" ) {
296+ return `<div class="pit-land">
297+ <p class="label">you asked for ${ esc ( landing . name ) } </p>
298+ <h2>${ tld } is yours. ${ name } is one click away.</h2>
299+ <p class="pit-copy">Register the name and point it at whatever should answer for it — a host, an
300+ address, or nothing yet.</p>
301+ <form method="post" action="/pit/${ esc ( landing . tld ) } /names" class="pit-row">
302+ ${ csrfInput ( req ) }
303+ <input type="hidden" name="label" value="${ esc ( landing . label ) } ">
304+ <span class="mono acid">${ esc ( landing . name ) } </span>
305+ <input name="target" placeholder="points at… (optional)" autocomplete="off">
306+ <button class="btn acid" type="submit">Register it</button>
307+ </form>
308+ </div>` ;
309+ }
310+
311+ if ( landing . kind === "yours" ) {
312+ return `<div class="pit-land">
313+ <p class="label">you asked for ${ esc ( landing . name ) } </p>
314+ <h2>${ name } is already yours.</h2>
315+ <p class="pit-copy">It is in your list below — change where it points, or release it.</p>
316+ </div>` ;
317+ }
318+
319+ if ( landing . kind === "taken" ) {
320+ return `<div class="pit-land">
321+ <p class="label">you asked for ${ esc ( landing . name ) } </p>
322+ <h2>${ name } is taken.</h2>
323+ <p class="pit-copy">Someone else holds ${ tld } and has minted this name${
324+ landing . target ? `, pointing it at <span class="mono">${ esc ( landing . target ) } </span>` : ""
325+ } . Claim an ending of your own below and you will never have to ask anyone for a name again.</p>
326+ </div>` ;
327+ }
328+
329+ if ( landing . kind === "buy" ) {
330+ const price = esc ( String ( landing . priceUsd ) ) ;
331+ return `<div class="pit-land">
332+ <p class="label">you asked for ${ esc ( landing . name ) } </p>
333+ <h2>${ name } is free. ${ tld } sells names at $${ price } .</h2>
334+ <p class="pit-copy">Buy it and it is yours to point wherever you like — the operator of the ending
335+ keeps the money, and nobody can take the name back.</p>
336+ ${ req . user ? `
337+ <form method="post" action="/pit/${ esc ( landing . tld ) } /buy" class="pit-row">
338+ ${ csrfInput ( req ) }
339+ <input type="hidden" name="label" value="${ esc ( landing . label ) } ">
340+ <span class="mono acid">${ esc ( landing . name ) } </span>
341+ <button class="btn acid" type="submit">Buy for $${ price } </button>
342+ </form>`
343+ : `<p class="pit-copy">Sign in with your moshcode account to buy it — the same login the CLI uses.</p>
344+ <p><a class="btn acid" href="/">Sign in →</a></p>` }
345+ </div>` ;
346+ }
347+
348+ // not-for-sale: the name is free, but the operator has not put a price on
349+ // names under their ending, and `quoteName` refuses without one. Saying so
350+ // beats a checkout button that dead-ends.
351+ return `<div class="pit-land">
352+ <p class="label">you asked for ${ esc ( landing . name ) } </p>
353+ <h2>${ name } is free, but ${ tld } is not selling.</h2>
354+ <p class="pit-copy">Whoever holds the ending has not put a price on names under it. Claim an ending
355+ of your own below — or take the same label under one that is selling.</p>
356+ </div>` ;
357+ } ;
358+
267359const PIT_CSS = `
268360.pit-form{display:flex;gap:10px;flex-wrap:wrap;align-items:stretch;margin:18px 0 8px}
269361.pit-field{display:flex;align-items:center;gap:2px;background:var(--surface);border:1px solid var(--line-2);
@@ -301,7 +393,11 @@ const PIT_CSS = `
301393.pit-steps{margin:0;padding-left:20px;line-height:1.8;max-width:66ch}
302394.pit-steps li{margin-bottom:10px}
303395.pit-steps code,.pit-copy code{font-family:var(--mono);color:var(--acid);font-size:.86em}
304- .pit-copy{max-width:66ch;color:var(--dim)}` ;
396+ .pit-copy{max-width:66ch;color:var(--dim)}
397+ .pit-land{border:1px solid var(--line-2);border-left:3px solid var(--acid);border-radius:var(--r);
398+ background:linear-gradient(180deg,var(--surface),var(--bg-tint));padding:20px 22px;margin:0 0 26px}
399+ .pit-land h2{font-size:1.35rem;text-transform:none;margin:6px 0 10px}
400+ .pit-land .pit-form,.pit-land .pit-row{margin-bottom:0}` ;
305401
306402/**
307403 * The tab strip. `/pit` is the namespace itself and `/pit/dns` is how you
@@ -323,6 +419,24 @@ moshpitRouter.get("/pit", async (req, res) => {
323419 req . user ? balance ( req . user . id ) : 0 ,
324420 ] ) ;
325421
422+ // `?name=mosh.whatever` — somebody typed a Moshpit name and ended up here
423+ // instead of at a site. Work out what they can actually do about it.
424+ const asked = parseMoshpitName ( req . query . name ) ;
425+ let landing = { kind : "none" } ;
426+ if ( asked ) {
427+ // With the price: a stranger can buy a name under an ending that is listed
428+ // for sale (#127), so the card has to know whether this one is.
429+ const owner = await getTldWithPrice ( asked . tld ) ;
430+ const entry = owner ? await getName ( asked . tld , asked . label ) : null ;
431+ landing = landingFor ( req . query . name , {
432+ tldOwned : Boolean ( owner ) ,
433+ ownedByViewer : Boolean ( owner && req . user && owner . user_id === req . user . id ) ,
434+ nameRegistered : Boolean ( entry ) ,
435+ target : entry ?. target ?? null ,
436+ priceUsd : owner ?. price_usd ?? null ,
437+ } ) ;
438+ }
439+
326440 // Exemptions are only meaningful for a TLD that points somewhere, so only
327441 // those cost a query.
328442 const exemptions = new Map ( ) ;
@@ -431,6 +545,7 @@ moshpitRouter.get("/pit", async (req, res) => {
431545 you exempt stays exactly where it is.
432546 </p>
433547 ${ pitTabs ( "namespace" ) }
548+ ${ landingCard ( req , landing ) }
434549 ${ msg }
435550 ${ req . user ? claimForm ( req ) : "" }
436551 <h2 style="margin-top:34px;font-size:1.2rem">Yours</h2>
0 commit comments