a tool for shared writing and social publishing

tweak custom domain logic to provide all records and proper verify flow

+151 -96
+15 -7
app/api/rpc/[command]/domain_routes.ts
··· 25 25 let errorResponse = e as NextApiResponse; 26 26 if (errorResponse.statusCode === 403) { 27 27 try { 28 - let verification = await vercel.projects.getProjectDomain({ 29 - idOrName: "prj_9jX4tmYCISnm176frFxk07fF74kG", 30 - teamId: "team_42xaJiZMTw9Sr7i0DcLTae9d", 31 - domain, 32 - }); 33 - if (!verification.verification) return {}; 28 + let [verification, config] = await Promise.all([ 29 + vercel.projects.getProjectDomain({ 30 + idOrName: "prj_9jX4tmYCISnm176frFxk07fF74kG", 31 + teamId: "team_42xaJiZMTw9Sr7i0DcLTae9d", 32 + domain, 33 + }), 34 + vercel.domains.getDomainConfig({ 35 + domain, 36 + teamId: "team_42xaJiZMTw9Sr7i0DcLTae9d", 37 + }), 38 + ]); 39 + if (!verification.verification) { 40 + return { config }; 41 + } 34 42 return { 35 - error: "Verification_needed", 36 43 verification: verification.verification, 44 + config, 37 45 } as const; 38 46 } catch (e) { 39 47 return { error: true };
+119 -83
app/lish/createPub/UpdatePubForm.tsx
··· 19 19 import { Verification } from "@vercel/sdk/esm/models/getprojectdomainop"; 20 20 import Link from "next/link"; 21 21 import { Checkbox } from "components/Checkbox"; 22 + import type { GetDomainConfigResponseBody } from "@vercel/sdk/esm/models/getdomainconfigop"; 22 23 23 24 export const EditPubForm = () => { 24 25 let { data } = usePublicationData(); ··· 188 189 let [state, setState] = useState< 189 190 | { type: "default" } 190 191 | { type: "addDomain" } 191 - | { type: "domainSettings"; domain: string; verification?: Verification[] } 192 + | { 193 + type: "domainSettings"; 194 + domain: string; 195 + verification?: Verification[]; 196 + config?: GetDomainConfigResponseBody; 197 + } 192 198 >({ type: "default" }); 193 199 let domains = pubData?.publication_domains || []; 194 200 ··· 208 214 ) : state.type === "domainSettings" ? ( 209 215 <DomainSettings 210 216 verification={state.verification} 217 + config={state.config} 211 218 domain={state.domain} 212 219 goBack={() => setState({ type: "default" })} 213 220 /> ··· 223 230 setState({ 224 231 type: "domainSettings", 225 232 domain: d.domain, 226 - verification: v, 233 + verification: v?.verification, 234 + config: v?.config, 227 235 }); 228 236 }} 229 237 /> ··· 312 320 domain: string; 313 321 base_path: string; 314 322 publication_uri: string; 315 - setDomain: (v?: Verification[]) => void; 323 + setDomain: (domain?: { 324 + verification?: Verification[]; 325 + config?: GetDomainConfigResponseBody; 326 + }) => void; 316 327 }) { 317 328 let { data } = useSWR(props.domain, async (domain) => { 318 329 return await callRPC("get_domain_status", { domain }); 319 330 }); 320 331 321 332 let pending = data?.config?.misconfigured || data?.error; 322 - console.log(props.domain, data); 323 333 324 334 return ( 325 335 <div className="text-sm text-secondary relative w-full "> ··· 329 339 <button 330 340 className="group/pending px-1 py-0.5 flex gap-1 items-center rounded-full hover:bg-accent-1 hover:text-accent-2 hover:outline-accent-1 border-transparent outline-solid outline-transparent selected-outline" 331 341 onClick={() => { 332 - if (data?.error === "Verification_needed") { 333 - props.setDomain(data.verification); 334 - } else { 335 - props.setDomain(); 336 - } 342 + props.setDomain(data); 337 343 }} 338 344 > 339 345 <p className="group-hover/pending:block hidden w-max pl-1 font-bold"> ··· 373 379 374 380 const DomainSettings = (props: { 375 381 domain: string; 382 + config?: GetDomainConfigResponseBody; 376 383 goBack: () => void; 377 384 verification?: Verification[]; 378 385 }) => { 386 + let { data, mutate } = useSWR(props.domain, async (domain) => { 387 + return await callRPC("get_domain_status", { domain }); 388 + }); 379 389 let isSubdomain = props.domain.split(".").length > 2; 380 - if (props.verification) 381 - return ( 382 - <div className="flex flex-col gap-[6px] text-sm"> 383 - <div>{props.domain} is in use on a Vercel account.</div> 384 - <div className="flex gap-3 p-1 border border-border-light rounded-md py-1"> 385 - <div className="flex flex-col "> 386 - <div className="text-tertiary">Type</div> 387 - <div>{props.verification[0].type}</div> 388 - </div> 389 - <div className="flex flex-col"> 390 - <div className="text-tertiary">Name</div> 391 - <div style={{ wordBreak: "break-word" }}> 392 - {props.verification[0].domain} 393 - </div> 394 - </div> 395 - <div className="flex flex-col"> 396 - <div className="text-tertiary">Value</div> 397 - <div style={{ wordBreak: "break-word" }}> 398 - {props.verification?.[0].value} 399 - </div> 400 - </div> 401 - </div> 402 - <div> 403 - <button 404 - className="text-accent-contrast w-fit" 405 - onClick={() => props.goBack()} 406 - > 407 - Back 408 - </button> 409 - </div> 410 - <button className="text-accent-contrast w-fit">verify</button> 411 - </div> 412 - ); 413 - 390 + if (!data) return; 391 + let { config, verification } = data; 392 + if (!config?.misconfigured && !verification) 393 + return <div>This domain is verified!</div>; 414 394 return ( 415 395 <div className="flex flex-col gap-[6px] text-sm"> 416 396 <div> 417 397 To verify this domain, add the following record to your DNS provider for{" "} 418 398 <strong>{props.domain}</strong>. 419 399 </div> 420 - 421 - {isSubdomain ? ( 422 - <div className="flex gap-3 p-1 border border-border-light rounded-md py-1"> 423 - <div className="flex flex-col "> 424 - <div className="text-tertiary">Type</div> 425 - <div>CNAME</div> 426 - </div> 427 - <div className="flex flex-col"> 428 - <div className="text-tertiary">Name</div> 429 - <div style={{ wordBreak: "break-word" }}> 430 - {props.domain.split(".").slice(0, -2).join(".")} 431 - </div> 432 - </div> 433 - <div className="flex flex-col"> 434 - <div className="text-tertiary">Value</div> 435 - <div style={{ wordBreak: "break-word" }}>cname.vercel-dns.com</div> 436 - </div> 437 - </div> 438 - ) : ( 439 - <div className="flex gap-3 px-2 py-1 border border-border-light rounded-md "> 440 - <div className="flex flex-col "> 441 - <div className="text-tertiary">Type</div> 442 - <div>A</div> 443 - </div> 444 - <div className="flex flex-col"> 445 - <div className="text-tertiary">Name</div> 446 - <div>@</div> 447 - </div> 448 - <div className="flex flex-col"> 449 - <div className="text-tertiary">Value</div> 450 - <div>76.76.21.21</div> 451 - </div> 452 - </div> 453 - )} 454 - <button 455 - className="text-accent-contrast w-fit" 456 - onClick={() => props.goBack()} 457 - > 458 - Back 459 - </button> 400 + <table className="border border-border-light rounded-md"> 401 + <thead> 402 + <tr> 403 + <th className="p-1 py-1 text-tertiary">Type</th> 404 + <th className="p-1 py-1 text-tertiary">Name</th> 405 + <th className="p-1 py-1 text-tertiary">Value</th> 406 + </tr> 407 + </thead> 408 + <tbody> 409 + {verification && ( 410 + <tr> 411 + <td className="p-1 py-1"> 412 + <div>{verification[0].type}</div> 413 + </td> 414 + <td className="p-1 py-1"> 415 + <div style={{ wordBreak: "break-word" }}> 416 + {verification[0].domain} 417 + </div> 418 + </td> 419 + <td className="p-1 py-1"> 420 + <div style={{ wordBreak: "break-word" }}> 421 + {verification?.[0].value} 422 + </div> 423 + </td> 424 + </tr> 425 + )} 426 + {config && 427 + (isSubdomain ? ( 428 + <tr> 429 + <td className="p-1 py-1"> 430 + <div>CNAME</div> 431 + </td> 432 + <td className="p-1 py-1"> 433 + <div style={{ wordBreak: "break-word" }}> 434 + {props.domain.split(".").slice(0, -2).join(".")} 435 + </div> 436 + </td> 437 + <td className="p-1 py-1"> 438 + <div style={{ wordBreak: "break-word" }}> 439 + { 440 + config?.recommendedCNAME.sort( 441 + (a, b) => a.rank - b.rank, 442 + )[0].value 443 + } 444 + </div> 445 + </td> 446 + </tr> 447 + ) : ( 448 + <tr> 449 + <td className="p-1 py-1"> 450 + <div>A</div> 451 + </td> 452 + <td className="p-1 py-1"> 453 + <div style={{ wordBreak: "break-word" }}>@</div> 454 + </td> 455 + <td className="p-1 py-1"> 456 + <div style={{ wordBreak: "break-word" }}> 457 + { 458 + config?.recommendedIPv4.sort((a, b) => a.rank - b.rank)[0] 459 + .value 460 + } 461 + </div> 462 + </td> 463 + </tr> 464 + ))} 465 + {config?.configuredBy === "CNAME" && config.recommendedCNAME[0] && ( 466 + <tr></tr> 467 + )} 468 + </tbody> 469 + </table> 470 + <div className="flex flex-row justify-between"> 471 + <button 472 + className="text-accent-contrast w-fit" 473 + onClick={() => props.goBack()} 474 + > 475 + Back 476 + </button> 477 + <VerifyButton verify={() => mutate()} /> 478 + </div> 460 479 </div> 461 480 ); 462 481 }; 482 + 483 + const VerifyButton = (props: { verify: () => Promise<any> }) => { 484 + let [loading, setLoading] = useState(false); 485 + return ( 486 + <button 487 + className="text-accent-contrast w-fit" 488 + onClick={async (e) => { 489 + e.preventDefault(); 490 + setLoading(true); 491 + await props.verify(); 492 + setLoading(false); 493 + }} 494 + > 495 + {loading ? <DotLoader /> : "verify"} 496 + </button> 497 + ); 498 + };
+16 -5
package-lock.json
··· 36 36 "@types/mdx": "^2.0.13", 37 37 "@vercel/analytics": "^1.5.0", 38 38 "@vercel/functions": "^2.2.12", 39 - "@vercel/sdk": "^1.3.1", 39 + "@vercel/sdk": "^1.11.4", 40 40 "babel-plugin-react-compiler": "^19.1.0-rc.1", 41 41 "base64-js": "^1.5.1", 42 42 "colorjs.io": "^0.5.2", ··· 6686 6686 } 6687 6687 }, 6688 6688 "node_modules/@vercel/sdk": { 6689 - "version": "1.3.1", 6690 - "resolved": "https://registry.npmjs.org/@vercel/sdk/-/sdk-1.3.1.tgz", 6691 - "integrity": "sha512-lTzG17DIDEJlWaGRNVyLZ17p8NRMOE2U98Kg96r1q79LGC8nPthoWhxupHXxGk3eAjjsvp8OA3bncKEyCHcofg==", 6689 + "version": "1.11.4", 6690 + "resolved": "https://registry.npmjs.org/@vercel/sdk/-/sdk-1.11.4.tgz", 6691 + "integrity": "sha512-TlLGQq6ToqnGhICQjvAyjSJkrHsLtqIM+nZuGV/SiamwSzXjiTpeTAe5wMDAao7LPJ9Efir/z76TvXFbLlW/9Q==", 6692 + "dependencies": { 6693 + "zod": "^3.20.0" 6694 + }, 6695 + "bin": { 6696 + "mcp": "bin/mcp-server.js" 6697 + }, 6692 6698 "peerDependencies": { 6693 - "zod": ">= 3" 6699 + "@modelcontextprotocol/sdk": ">=1.5.0 <1.10.0" 6700 + }, 6701 + "peerDependenciesMeta": { 6702 + "@modelcontextprotocol/sdk": { 6703 + "optional": true 6704 + } 6694 6705 } 6695 6706 }, 6696 6707 "node_modules/abort-controller": {
+1 -1
package.json
··· 46 46 "@types/mdx": "^2.0.13", 47 47 "@vercel/analytics": "^1.5.0", 48 48 "@vercel/functions": "^2.2.12", 49 - "@vercel/sdk": "^1.3.1", 49 + "@vercel/sdk": "^1.11.4", 50 50 "babel-plugin-react-compiler": "^19.1.0-rc.1", 51 51 "base64-js": "^1.5.1", 52 52 "colorjs.io": "^0.5.2",