@volter/world-ui 0.1.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.
Files changed (44) hide show
  1. package/LICENSE +202 -0
  2. package/client/brand/fonts/Geist-Variable.woff2 +0 -0
  3. package/client/brand/fonts/GeistMono-Variable.woff2 +0 -0
  4. package/client/brand/fonts/InstrumentSerif-Italic.woff2 +0 -0
  5. package/client/brand/fonts/InstrumentSerif-Regular.woff2 +0 -0
  6. package/client/brand/fonts/LICENSES.txt +191 -0
  7. package/client/brand/tokens.css +212 -0
  8. package/client/consent.tsx +96 -0
  9. package/client/kit.css +55 -0
  10. package/client/kit.tsx +312 -0
  11. package/client/payment.tsx +121 -0
  12. package/client/portal.tsx +102 -0
  13. package/client/sign-in.tsx +90 -0
  14. package/dist/client/brand/fonts/Geist-Variable.woff2 +0 -0
  15. package/dist/client/brand/fonts/GeistMono-Variable.woff2 +0 -0
  16. package/dist/client/brand/fonts/InstrumentSerif-Italic.woff2 +0 -0
  17. package/dist/client/brand/fonts/InstrumentSerif-Regular.woff2 +0 -0
  18. package/dist/client/brand/fonts/LICENSES.txt +191 -0
  19. package/dist/client/brand/tokens.css +212 -0
  20. package/dist/client/consent.bundle.js +36 -0
  21. package/dist/client/consent.d.ts +47 -0
  22. package/dist/client/consent.js +24 -0
  23. package/dist/client/consent.tsx +96 -0
  24. package/dist/client/kit.bundle.js +235 -0
  25. package/dist/client/kit.css +55 -0
  26. package/dist/client/kit.d.ts +101 -0
  27. package/dist/client/kit.js +160 -0
  28. package/dist/client/kit.tsx +312 -0
  29. package/dist/client/payment.bundle.js +77 -0
  30. package/dist/client/payment.d.ts +57 -0
  31. package/dist/client/payment.js +55 -0
  32. package/dist/client/payment.tsx +121 -0
  33. package/dist/client/portal.bundle.js +39 -0
  34. package/dist/client/portal.d.ts +44 -0
  35. package/dist/client/portal.js +29 -0
  36. package/dist/client/portal.tsx +102 -0
  37. package/dist/client/sign-in.bundle.js +28 -0
  38. package/dist/client/sign-in.d.ts +43 -0
  39. package/dist/client/sign-in.js +28 -0
  40. package/dist/client/sign-in.tsx +90 -0
  41. package/dist/src/index.d.ts +33 -0
  42. package/dist/src/index.js +56 -0
  43. package/package.json +45 -0
  44. package/src/index.ts +72 -0
@@ -0,0 +1,102 @@
1
+ // A HOSTED ACCOUNT PORTAL — the hosted flow a billing vendor gives an application's customer to manage what
2
+ // they pay for (docs/contributing/architecture.md, "Screens"): the merchant and the way back, then sections of
3
+ // what the customer holds (a plan, a payment method, their details, past invoices), each item with the moves
4
+ // the vendor offers on it. Rendered on the server; every move is a form that posts and comes back to the page,
5
+ // with no script. A pack gives the words, the items and their moves; its skin gives the look. The same layout is a
6
+ // vendor's settings page a person works in (an API keys page): its forms make new items from fields the person fills.
7
+ import type { PaymentField } from './payment.tsx';
8
+ /** A move on an item: a form that posts, or with `method: 'get'` a button that opens another page (a link a person follows). */
9
+ export type PortalAction = { label: string; action: string; fields?: Record<string, string>; tone?: 'danger' | 'primary'; method?: 'get' | 'post' };
10
+ export type PortalItem = { title: string; detail?: string; note?: string; badge?: string; actions?: PortalAction[] };
11
+ export type PortalSection = { heading: string; items: PortalItem[]; empty?: string };
12
+ /** A form that makes something new from fields the person fills, named the vendor's way. */
13
+ export type PortalForm = { heading: string; action: string; fields: PaymentField[]; submit: { label: string } };
14
+
15
+ export type PortalProps = {
16
+ merchant: string;
17
+ back?: { href: string; label: string };
18
+ /** a line at the top after a move ("Your plan will be canceled on May 1") */
19
+ notice?: string;
20
+ sections: PortalSection[];
21
+ forms?: PortalForm[];
22
+ };
23
+
24
+ export function Portal(props: PortalProps) {
25
+ return (
26
+ <div className="portal">
27
+ <aside className="portal-side">
28
+ <div className="portal-merchant">{props.merchant}</div>
29
+ {props.back ? <a className="portal-back" href={props.back.href}>{props.back.label}</a> : null}
30
+ </aside>
31
+ <main className="portal-main">
32
+ {props.notice ? <p className="portal-notice" role="status">{props.notice}</p> : null}
33
+ {props.sections.map((s) => (
34
+ <section key={s.heading} className="portal-section">
35
+ <h2>{s.heading}</h2>
36
+ {s.items.length === 0 && s.empty ? <p className="portal-empty">{s.empty}</p> : null}
37
+ <ul>
38
+ {s.items.map((item, i) => (
39
+ <li key={`${item.title}:${i}`} className="portal-item">
40
+ <div className="portal-item-text">
41
+ <strong>{item.title}</strong>
42
+ {item.badge ? <span className="portal-badge">{item.badge}</span> : null}
43
+ {item.detail ? <span className="portal-detail">{item.detail}</span> : null}
44
+ {item.note ? <span className="portal-note">{item.note}</span> : null}
45
+ </div>
46
+ <div className="portal-actions">
47
+ {(item.actions ?? []).map((a) => (
48
+ <form key={a.label} method={a.method ?? 'post'} action={a.action}>
49
+ {Object.entries(a.fields ?? {}).map(([k, v]) => <input key={k} type="hidden" name={k} value={v} />)}
50
+ <button type="submit" className={`portal-button${a.tone ? ` portal-${a.tone}` : ''}`}>{a.label}</button>
51
+ </form>
52
+ ))}
53
+ </div>
54
+ </li>
55
+ ))}
56
+ </ul>
57
+ </section>
58
+ ))}
59
+ {(props.forms ?? []).map((f) => (
60
+ <section key={f.heading} className="portal-section">
61
+ <h2>{f.heading}</h2>
62
+ <form method="post" action={f.action} className="portal-form">
63
+ {f.fields.map((field) => (
64
+ <label key={field.id} className="portal-field" htmlFor={field.id}>
65
+ <span>{field.label}</span>
66
+ {field.options
67
+ ? <select id={field.id} name={field.id} defaultValue={field.value}>{field.options.map((o) => <option key={o.value} value={o.value}>{o.label}</option>)}</select>
68
+ : <input id={field.id} name={field.id} type={field.type ?? 'text'} placeholder={field.placeholder} defaultValue={field.value} />}
69
+ </label>
70
+ ))}
71
+ <button type="submit" className="portal-button portal-primary">{f.submit.label}</button>
72
+ </form>
73
+ </section>
74
+ ))}
75
+ </main>
76
+ </div>
77
+ );
78
+ }
79
+
80
+ /** The portal's layout; a pack's skin sets the colours and type over it. */
81
+ export const PORTAL_CSS = `
82
+ body { margin: 0; font: 14px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif; }
83
+ .portal { display: grid; grid-template-columns: 1fr 2fr; min-height: 100vh; }
84
+ @media (max-width: 800px) { .portal { grid-template-columns: 1fr; } }
85
+ .portal-side { padding: 48px; }
86
+ .portal-merchant { font-weight: 600; font-size: 18px; margin-bottom: 16px; }
87
+ .portal-back { text-decoration: none; }
88
+ .portal-main { padding: 48px; max-width: 640px; }
89
+ .portal-notice { padding: 12px 16px; border-radius: 6px; margin: 0 0 24px; }
90
+ .portal-section { margin-bottom: 32px; }
91
+ .portal-section h2 { font-size: 13px; font-weight: 600; text-transform: uppercase; letter-spacing: .04em; margin: 0 0 8px; padding-bottom: 8px; border-bottom: 1px solid; }
92
+ .portal-section ul { list-style: none; padding: 0; margin: 0; }
93
+ .portal-item { display: flex; justify-content: space-between; gap: 16px; padding: 12px 0; }
94
+ .portal-item-text { display: flex; flex-direction: column; }
95
+ .portal-badge { align-self: flex-start; font-size: 12px; padding: 0 6px; border-radius: 4px; margin: 2px 0; }
96
+ .portal-detail, .portal-note { font-size: 13px; }
97
+ .portal-actions { display: flex; gap: 8px; align-items: flex-start; }
98
+ .portal-button { padding: 6px 12px; border-radius: 6px; border: 1px solid; font: inherit; cursor: pointer; background: transparent; }
99
+ .portal-form { display: flex; flex-direction: column; gap: 12px; align-items: flex-start; }
100
+ .portal-field { display: flex; flex-direction: column; gap: 4px; width: 100%; }
101
+ .portal-field input, .portal-field select { padding: 8px 10px; border: 1px solid; border-radius: 6px; font: inherit; }
102
+ `;
@@ -0,0 +1,28 @@
1
+ var mB=Object.create;var{getPrototypeOf:uB,defineProperty:vB,getOwnPropertyNames:sB}=Object;var dB=Object.prototype.hasOwnProperty;function yB(W){return this[W]}var cB,iB,JB=(W,P,w)=>{var F=W!=null&&typeof W==="object";if(F){var C=P?cB??=new WeakMap:iB??=new WeakMap,I=C.get(W);if(I)return I}w=W!=null?mB(uB(W)):{};let A=P||!W||!W.__esModule?vB(w,"default",{value:W,enumerable:!0}):w;for(let x of sB(W))if(!dB.call(A,x))vB(A,x,{get:yB.bind(W,x),enumerable:!0});if(F)C.set(W,A);return A};var CB=(W,P)=>()=>(P||W((P={exports:{}}).exports,P),P.exports);var gB=CB((pB,XB)=>{(function(){function W(B,G){Object.defineProperty(F.prototype,B,{get:function(){console.warn("%s(...) is deprecated in plain JavaScript React classes. %s",G[0],G[1])}})}function P(B){if(B===null||typeof B!=="object")return null;return B=PB&&B[PB]||B["@@iterator"],typeof B==="function"?B:null}function w(B,G){B=(B=B.constructor)&&(B.displayName||B.name)||"ReactClass";var M=B+"."+G;wB[M]||(console.error("Can't call %s on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to `this.state` directly or define a `state = {};` class property with the desired state in the %s component.",G,B),wB[M]=!0)}function F(B,G,M){this.props=B,this.context=G,this.refs=OB,this.updater=M||DB}function C(){}function I(B,G,M){this.props=B,this.context=G,this.refs=OB,this.updater=M||DB}function A(){}function x(B){return""+B}function b(B){try{x(B);var G=!1}catch(U){G=!0}if(G){G=console;var M=G.error,R=typeof Symbol==="function"&&Symbol.toStringTag&&B[Symbol.toStringTag]||B.constructor.name||"Object";return M.call(G,"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",R),x(B)}}function h(B){if(B==null)return null;if(typeof B==="function")return B.$$typeof===SB?null:B.displayName||B.name||null;if(typeof B==="string")return B;switch(B){case s:return"Fragment";case Y:return"Profiler";case H:return"StrictMode";case d:return"Suspense";case q:return"SuspenseList";case HB:return"Activity"}if(typeof B==="object")switch(typeof B.tag==="number"&&console.error("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."),B.$$typeof){case GB:return"Portal";case D:return B.displayName||"Context";case O:return(B._context.displayName||"Context")+".Consumer";case T:var G=B.render;return B=B.displayName,B||(B=G.displayName||G.name||"",B=B!==""?"ForwardRef("+B+")":"ForwardRef"),B;case E:return G=B.displayName||null,G!==null?G:h(B.type)||"Memo";case N:G=B._payload,B=B._init;try{return h(B(G))}catch(M){}}return null}function e(B){if(B===s)return"<>";if(typeof B==="object"&&B!==null&&B.$$typeof===N)return"<...>";try{var G=h(B);return G?"<"+G+">":"<...>"}catch(M){return"<...>"}}function c(){var B=Z.A;return B===null?null:B.getOwner()}function i(){return Error("react-stack-top-frame")}function p(B){if(MB.call(B,"key")){var G=Object.getOwnPropertyDescriptor(B,"key").get;if(G&&G.isReactWarning)return!1}return B.key!==void 0}function YB(B,G){function M(){TB||(TB=!0,console.error("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",G))}M.isReactWarning=!0,Object.defineProperty(B,"key",{get:M,configurable:!0})}function o(){var B=h(this.type);return IB[B]||(IB[B]=!0,console.error("Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release.")),B=this.props.ref,B!==void 0?B:null}function l(B,G,M,R,U,z){var X=M.ref;return B={$$typeof:a,type:B,key:G,props:M,_owner:R},(X!==void 0?X:null)!==null?Object.defineProperty(B,"ref",{enumerable:!1,get:o}):Object.defineProperty(B,"ref",{enumerable:!1,value:null}),B._store={},Object.defineProperty(B._store,"validated",{configurable:!1,enumerable:!1,writable:!0,value:0}),Object.defineProperty(B,"_debugInfo",{configurable:!1,enumerable:!1,writable:!0,value:null}),Object.defineProperty(B,"_debugStack",{configurable:!1,enumerable:!1,writable:!0,value:U}),Object.defineProperty(B,"_debugTask",{configurable:!1,enumerable:!1,writable:!0,value:z}),Object.freeze&&(Object.freeze(B.props),Object.freeze(B)),B}function ZB(B,G){return G=l(B.type,G,B.props,B._owner,B._debugStack,B._debugTask),B._store&&(G._store.validated=B._store.validated),G}function BB(B){j(B)?B._store&&(B._store.validated=1):typeof B==="object"&&B!==null&&B.$$typeof===N&&(B._payload.status==="fulfilled"?j(B._payload.value)&&B._payload.value._store&&(B._payload.value._store.validated=1):B._store&&(B._store.validated=1))}function j(B){return typeof B==="object"&&B!==null&&B.$$typeof===a}function zB(B){var G={"=":"=0",":":"=2"};return"$"+B.replace(/[=:]/g,function(M){return G[M]})}function t(B,G){return typeof B==="object"&&B!==null&&B.key!=null?(b(B.key),zB(""+B.key)):G.toString(36)}function QB(B){switch(B.status){case"fulfilled":return B.value;case"rejected":throw B.reason;default:switch(typeof B.status==="string"?B.then(A,A):(B.status="pending",B.then(function(G){B.status==="pending"&&(B.status="fulfilled",B.value=G)},function(G){B.status==="pending"&&(B.status="rejected",B.reason=G)})),B.status){case"fulfilled":return B.value;case"rejected":throw B.reason}}throw B}function _(B,G,M,R,U){var z=typeof B;if(z==="undefined"||z==="boolean")B=null;var X=!1;if(B===null)X=!0;else switch(z){case"bigint":case"string":case"number":X=!0;break;case"object":switch(B.$$typeof){case a:case GB:X=!0;break;case N:return X=B._init,_(X(B._payload),G,M,R,U)}}if(X){X=B,U=U(X);var L=R===""?"."+t(X,0):R;return KB(U)?(M="",L!=null&&(M=L.replace(xB,"$&/")+"/"),_(U,G,M,"",function(v){return v})):U!=null&&(j(U)&&(U.key!=null&&(X&&X.key===U.key||b(U.key)),M=ZB(U,M+(U.key==null||X&&X.key===U.key?"":(""+U.key).replace(xB,"$&/")+"/")+L),R!==""&&X!=null&&j(X)&&X.key==null&&X._store&&!X._store.validated&&(M._store.validated=2),U=M),G.push(U)),1}if(X=0,L=R===""?".":R+":",KB(B))for(var Q=0;Q<B.length;Q++)R=B[Q],z=L+t(R,Q),X+=_(R,G,M,z,U);else if(Q=P(B),typeof Q==="function")for(Q===B.entries&&(NB||console.warn("Using Maps as children is not supported. Use an array of keyed ReactElements instead."),NB=!0),B=Q.call(B),Q=0;!(R=B.next()).done;)R=R.value,z=L+t(R,Q++),X+=_(R,G,M,z,U);else if(z==="object"){if(typeof B.then==="function")return _(QB(B),G,M,R,U);throw G=String(B),Error("Objects are not valid as a React child (found: "+(G==="[object Object]"?"object with keys {"+Object.keys(B).join(", ")+"}":G)+"). If you meant to render a collection of children, use an array instead.")}return X}function V(B,G,M){if(B==null)return B;var R=[],U=0;return _(B,R,"","",function(z){return G.call(M,z,U++)}),R}function LB(B){if(B._status===-1){var G=B._ioInfo;G!=null&&(G.start=G.end=performance.now()),G=B._result;var M=G();if(M.then(function(U){if(B._status===0||B._status===-1){B._status=1,B._result=U;var z=B._ioInfo;z!=null&&(z.end=performance.now()),M.status===void 0&&(M.status="fulfilled",M.value=U)}},function(U){if(B._status===0||B._status===-1){B._status=2,B._result=U;var z=B._ioInfo;z!=null&&(z.end=performance.now()),M.status===void 0&&(M.status="rejected",M.reason=U)}}),G=B._ioInfo,G!=null){G.value=M;var R=M.displayName;typeof R==="string"&&(G.name=R)}B._status===-1&&(B._status=0,B._result=M)}if(B._status===1)return G=B._result,G===void 0&&console.error(`lazy: Expected the result of a dynamic import() call. Instead received: %s
2
+
3
+ Your code should look like:
4
+ const MyComponent = lazy(() => import('./MyComponent'))
5
+
6
+ Did you accidentally put curly braces around the import?`,G),"default"in G||console.error(`lazy: Expected the result of a dynamic import() call. Instead received: %s
7
+
8
+ Your code should look like:
9
+ const MyComponent = lazy(() => import('./MyComponent'))`,G),G.default;throw B._result}function $(){var B=Z.H;return B===null&&console.error(`Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
10
+ 1. You might have mismatching versions of React and the renderer (such as React DOM)
11
+ 2. You might be breaking the Rules of Hooks
12
+ 3. You might have more than one copy of React in the same app
13
+ See https://react.dev/link/invalid-hook-call for tips about how to debug and fix this problem.`),B}function n(){Z.asyncTransitions--}function g(B){if(RB===null)try{var G=("require"+Math.random()).slice(0,7);RB=(XB&&XB[G]).call(XB,"timers").setImmediate}catch(M){RB=function(R){_B===!1&&(_B=!0,typeof MessageChannel>"u"&&console.error("This browser does not have a MessageChannel implementation, so enqueuing tasks via await act(async () => ...) will fail. Please file an issue at https://github.com/facebook/react/issues if you encounter this warning."));var U=new MessageChannel;U.port1.onmessage=R,U.port2.postMessage(void 0)}}return RB(B)}function S(B){return 1<B.length&&typeof AggregateError==="function"?AggregateError(B):B[0]}function k(B,G){G!==UB-1&&console.error("You seem to have overlapping act() calls, this is not supported. Be sure to await previous act() calls before making a new one. "),UB=G}function m(B,G,M){var R=Z.actQueue;if(R!==null)if(R.length!==0)try{u(R),g(function(){return m(B,G,M)});return}catch(U){Z.thrownErrors.push(U)}else Z.actQueue=null;0<Z.thrownErrors.length?(R=S(Z.thrownErrors),Z.thrownErrors.length=0,M(R)):G(B)}function u(B){if(!$B){$B=!0;var G=0;try{for(;G<B.length;G++){var M=B[G];do{Z.didUsePromise=!1;var R=M(!1);if(R!==null){if(Z.didUsePromise){B[G]=M,B.splice(0,G);return}M=R}else break}while(1)}B.length=0}catch(U){B.splice(0,G+1),Z.thrownErrors.push(U)}finally{$B=!1}}}typeof __REACT_DEVTOOLS_GLOBAL_HOOK__<"u"&&typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart==="function"&&__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(Error());var a=Symbol.for("react.transitional.element"),GB=Symbol.for("react.portal"),s=Symbol.for("react.fragment"),H=Symbol.for("react.strict_mode"),Y=Symbol.for("react.profiler"),O=Symbol.for("react.consumer"),D=Symbol.for("react.context"),T=Symbol.for("react.forward_ref"),d=Symbol.for("react.suspense"),q=Symbol.for("react.suspense_list"),E=Symbol.for("react.memo"),N=Symbol.for("react.lazy"),HB=Symbol.for("react.activity"),PB=Symbol.iterator,wB={},DB={isMounted:function(){return!1},enqueueForceUpdate:function(B){w(B,"forceUpdate")},enqueueReplaceState:function(B){w(B,"replaceState")},enqueueSetState:function(B){w(B,"setState")}},FB=Object.assign,OB={};Object.freeze(OB),F.prototype.isReactComponent={},F.prototype.setState=function(B,G){if(typeof B!=="object"&&typeof B!=="function"&&B!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,B,G,"setState")},F.prototype.forceUpdate=function(B){this.updater.enqueueForceUpdate(this,B,"forceUpdate")};var K={isMounted:["isMounted","Instead, make sure to clean up subscriptions and pending requests in componentWillUnmount to prevent memory leaks."],replaceState:["replaceState","Refactor your code to use setState instead (see https://github.com/facebook/react/issues/3236)."]};for(r in K)K.hasOwnProperty(r)&&W(r,K[r]);C.prototype=F.prototype,K=I.prototype=new C,K.constructor=I,FB(K,F.prototype),K.isPureReactComponent=!0;var KB=Array.isArray,SB=Symbol.for("react.client.reference"),Z={H:null,A:null,T:null,S:null,actQueue:null,asyncTransitions:0,isBatchingLegacy:!1,didScheduleLegacyUpdate:!1,didUsePromise:!1,thrownErrors:[],getCurrentStack:null,recentlyCreatedOwnerStacks:0},MB=Object.prototype.hasOwnProperty,AB=console.createTask?console.createTask:function(){return null};K={react_stack_bottom_frame:function(B){return B()}};var TB,EB,IB={},kB=K.react_stack_bottom_frame.bind(K,i)(),fB=AB(e(i)),NB=!1,xB=/\/+/g,jB=typeof reportError==="function"?reportError:function(B){if(typeof window==="object"&&typeof window.ErrorEvent==="function"){var G=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:typeof B==="object"&&B!==null&&typeof B.message==="string"?String(B.message):String(B),error:B});if(!window.dispatchEvent(G))return}else if(typeof process==="object"&&typeof process.emit==="function"){process.emit("uncaughtException",B);return}console.error(B)},_B=!1,RB=null,UB=0,WB=!1,$B=!1,VB=typeof queueMicrotask==="function"?function(B){queueMicrotask(function(){return queueMicrotask(B)})}:g;K=Object.freeze({__proto__:null,c:function(B){return $().useMemoCache(B)}});var r={map:V,forEach:function(B,G,M){V(B,function(){G.apply(this,arguments)},M)},count:function(B){var G=0;return V(B,function(){G++}),G},toArray:function(B){return V(B,function(G){return G})||[]},only:function(B){if(!j(B))throw Error("React.Children.only expected to receive a single React element child.");return B}};pB.Activity=HB,pB.Children=r,pB.Component=F,pB.Fragment=s,pB.Profiler=Y,pB.PureComponent=I,pB.StrictMode=H,pB.Suspense=d,pB.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=Z,pB.__COMPILER_RUNTIME=K,pB.act=function(B){var G=Z.actQueue,M=UB;UB++;var R=Z.actQueue=G!==null?G:[],U=!1;try{var z=B()}catch(Q){Z.thrownErrors.push(Q)}if(0<Z.thrownErrors.length)throw k(G,M),B=S(Z.thrownErrors),Z.thrownErrors.length=0,B;if(z!==null&&typeof z==="object"&&typeof z.then==="function"){var X=z;return VB(function(){U||WB||(WB=!0,console.error("You called act(async () => ...) without await. This could lead to unexpected testing behaviour, interleaving multiple act calls and mixing their scopes. You should - await act(async () => ...);"))}),{then:function(Q,v){U=!0,X.then(function(y){if(k(G,M),M===0){try{u(R),g(function(){return m(y,Q,v)})}catch(hB){Z.thrownErrors.push(hB)}if(0<Z.thrownErrors.length){var bB=S(Z.thrownErrors);Z.thrownErrors.length=0,v(bB)}}else Q(y)},function(y){k(G,M),0<Z.thrownErrors.length?(y=S(Z.thrownErrors),Z.thrownErrors.length=0,v(y)):v(y)})}}}var L=z;if(k(G,M),M===0&&(u(R),R.length!==0&&VB(function(){U||WB||(WB=!0,console.error("A component suspended inside an `act` scope, but the `act` call was not awaited. When testing React components that depend on asynchronous data, you must await the result:\n\nawait act(() => ...)"))}),Z.actQueue=null),0<Z.thrownErrors.length)throw B=S(Z.thrownErrors),Z.thrownErrors.length=0,B;return{then:function(Q,v){U=!0,M===0?(Z.actQueue=R,g(function(){return m(L,Q,v)})):Q(L)}}},pB.cache=function(B){return function(){return B.apply(null,arguments)}},pB.cacheSignal=function(){return null},pB.captureOwnerStack=function(){var B=Z.getCurrentStack;return B===null?null:B()},pB.cloneElement=function(B,G,M){if(B===null||B===void 0)throw Error("The argument must be a React element, but you passed "+B+".");var R=FB({},B.props),U=B.key,z=B._owner;if(G!=null){var X;B:{if(MB.call(G,"ref")&&(X=Object.getOwnPropertyDescriptor(G,"ref").get)&&X.isReactWarning){X=!1;break B}X=G.ref!==void 0}X&&(z=c()),p(G)&&(b(G.key),U=""+G.key);for(L in G)!MB.call(G,L)||L==="key"||L==="__self"||L==="__source"||L==="ref"&&G.ref===void 0||(R[L]=G[L])}var L=arguments.length-2;if(L===1)R.children=M;else if(1<L){X=Array(L);for(var Q=0;Q<L;Q++)X[Q]=arguments[Q+2];R.children=X}R=l(B.type,U,R,z,B._debugStack,B._debugTask);for(U=2;U<arguments.length;U++)BB(arguments[U]);return R},pB.createContext=function(B){return B={$$typeof:D,_currentValue:B,_currentValue2:B,_threadCount:0,Provider:null,Consumer:null},B.Provider=B,B.Consumer={$$typeof:O,_context:B},B._currentRenderer=null,B._currentRenderer2=null,B},pB.createElement=function(B,G,M){for(var R=2;R<arguments.length;R++)BB(arguments[R]);R={};var U=null;if(G!=null)for(Q in EB||!("__self"in G)||"key"in G||(EB=!0,console.warn("Your app (or one of its dependencies) is using an outdated JSX transform. Update to the modern JSX transform for faster performance: https://react.dev/link/new-jsx-transform")),p(G)&&(b(G.key),U=""+G.key),G)MB.call(G,Q)&&Q!=="key"&&Q!=="__self"&&Q!=="__source"&&(R[Q]=G[Q]);var z=arguments.length-2;if(z===1)R.children=M;else if(1<z){for(var X=Array(z),L=0;L<z;L++)X[L]=arguments[L+2];Object.freeze&&Object.freeze(X),R.children=X}if(B&&B.defaultProps)for(Q in z=B.defaultProps,z)R[Q]===void 0&&(R[Q]=z[Q]);U&&YB(R,typeof B==="function"?B.displayName||B.name||"Unknown":B);var Q=1e4>Z.recentlyCreatedOwnerStacks++;return l(B,U,R,c(),Q?Error("react-stack-top-frame"):kB,Q?AB(e(B)):fB)},pB.createRef=function(){var B={current:null};return Object.seal(B),B},pB.forwardRef=function(B){B!=null&&B.$$typeof===E?console.error("forwardRef requires a render function but received a `memo` component. Instead of forwardRef(memo(...)), use memo(forwardRef(...))."):typeof B!=="function"?console.error("forwardRef requires a render function but was given %s.",B===null?"null":typeof B):B.length!==0&&B.length!==2&&console.error("forwardRef render functions accept exactly two parameters: props and ref. %s",B.length===1?"Did you forget to use the ref parameter?":"Any additional parameter will be undefined."),B!=null&&B.defaultProps!=null&&console.error("forwardRef render functions do not support defaultProps. Did you accidentally pass a React component?");var G={$$typeof:T,render:B},M;return Object.defineProperty(G,"displayName",{enumerable:!1,configurable:!0,get:function(){return M},set:function(R){M=R,B.name||B.displayName||(Object.defineProperty(B,"name",{value:R}),B.displayName=R)}}),G},pB.isValidElement=j,pB.lazy=function(B){B={_status:-1,_result:B};var G={$$typeof:N,_payload:B,_init:LB},M={name:"lazy",start:-1,end:-1,value:null,owner:null,debugStack:Error("react-stack-top-frame"),debugTask:console.createTask?console.createTask("lazy()"):null};return B._ioInfo=M,G._debugInfo=[{awaited:M}],G},pB.memo=function(B,G){B==null&&console.error("memo: The first argument must be a component. Instead received: %s",B===null?"null":typeof B),G={$$typeof:E,type:B,compare:G===void 0?null:G};var M;return Object.defineProperty(G,"displayName",{enumerable:!1,configurable:!0,get:function(){return M},set:function(R){M=R,B.name||B.displayName||(Object.defineProperty(B,"name",{value:R}),B.displayName=R)}}),G},pB.startTransition=function(B){var G=Z.T,M={};M._updatedFibers=new Set,Z.T=M;try{var R=B(),U=Z.S;U!==null&&U(M,R),typeof R==="object"&&R!==null&&typeof R.then==="function"&&(Z.asyncTransitions++,R.then(n,n),R.then(A,jB))}catch(z){jB(z)}finally{G===null&&M._updatedFibers&&(B=M._updatedFibers.size,M._updatedFibers.clear(),10<B&&console.warn("Detected a large number of updates inside startTransition. If this is due to a subscription please re-write it to use React provided hooks. Otherwise concurrent mode guarantees are off the table.")),G!==null&&M.types!==null&&(G.types!==null&&G.types!==M.types&&console.error("We expected inner Transitions to have transferred the outer types set and that you cannot add to the outer Transition while inside the inner.This is a bug in React."),G.types=M.types),Z.T=G}},pB.unstable_useCacheRefresh=function(){return $().useCacheRefresh()},pB.use=function(B){return $().use(B)},pB.useActionState=function(B,G,M){return $().useActionState(B,G,M)},pB.useCallback=function(B,G){return $().useCallback(B,G)},pB.useContext=function(B){var G=$();return B.$$typeof===O&&console.error("Calling useContext(Context.Consumer) is not supported and will cause bugs. Did you mean to call useContext(Context) instead?"),G.useContext(B)},pB.useDebugValue=function(B,G){return $().useDebugValue(B,G)},pB.useDeferredValue=function(B,G){return $().useDeferredValue(B,G)},pB.useEffect=function(B,G){return B==null&&console.warn("React Hook useEffect requires an effect callback. Did you forget to pass a callback to the hook?"),$().useEffect(B,G)},pB.useEffectEvent=function(B){return $().useEffectEvent(B)},pB.useId=function(){return $().useId()},pB.useImperativeHandle=function(B,G,M){return $().useImperativeHandle(B,G,M)},pB.useInsertionEffect=function(B,G){return B==null&&console.warn("React Hook useInsertionEffect requires an effect callback. Did you forget to pass a callback to the hook?"),$().useInsertionEffect(B,G)},pB.useLayoutEffect=function(B,G){return B==null&&console.warn("React Hook useLayoutEffect requires an effect callback. Did you forget to pass a callback to the hook?"),$().useLayoutEffect(B,G)},pB.useMemo=function(B,G){return $().useMemo(B,G)},pB.useOptimistic=function(B,G){return $().useOptimistic(B,G)},pB.useReducer=function(B,G,M){return $().useReducer(B,G,M)},pB.useRef=function(B){return $().useRef(B)},pB.useState=function(B){return $().useState(B)},pB.useSyncExternalStore=function(B,G,M){return $().useSyncExternalStore(B,G,M)},pB.useTransition=function(){return $().useTransition()},pB.version="19.2.7",typeof __REACT_DEVTOOLS_GLOBAL_HOOK__<"u"&&typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop==="function"&&__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(Error())})()});var qB=CB((oB)=>{var f=JB(gB());(function(){function W(H){if(H==null)return null;if(typeof H==="function")return H.$$typeof===$?null:H.displayName||H.name||null;if(typeof H==="string")return H;switch(H){case o:return"Fragment";case ZB:return"Profiler";case l:return"StrictMode";case t:return"Suspense";case QB:return"SuspenseList";case LB:return"Activity"}if(typeof H==="object")switch(typeof H.tag==="number"&&console.error("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."),H.$$typeof){case YB:return"Portal";case j:return H.displayName||"Context";case BB:return(H._context.displayName||"Context")+".Consumer";case zB:var Y=H.render;return H=H.displayName,H||(H=Y.displayName||Y.name||"",H=H!==""?"ForwardRef("+H+")":"ForwardRef"),H;case _:return Y=H.displayName||null,Y!==null?Y:W(H.type)||"Memo";case V:Y=H._payload,H=H._init;try{return W(H(Y))}catch(O){}}return null}function P(H){return""+H}function w(H){try{P(H);var Y=!1}catch(T){Y=!0}if(Y){Y=console;var O=Y.error,D=typeof Symbol==="function"&&Symbol.toStringTag&&H[Symbol.toStringTag]||H.constructor.name||"Object";return O.call(Y,"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",D),P(H)}}function F(H){if(H===o)return"<>";if(typeof H==="object"&&H!==null&&H.$$typeof===V)return"<...>";try{var Y=W(H);return Y?"<"+Y+">":"<...>"}catch(O){return"<...>"}}function C(){var H=n.A;return H===null?null:H.getOwner()}function I(){return Error("react-stack-top-frame")}function A(H){if(g.call(H,"key")){var Y=Object.getOwnPropertyDescriptor(H,"key").get;if(Y&&Y.isReactWarning)return!1}return H.key!==void 0}function x(H,Y){function O(){m||(m=!0,console.error("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",Y))}O.isReactWarning=!0,Object.defineProperty(H,"key",{get:O,configurable:!0})}function b(){var H=W(this.type);return u[H]||(u[H]=!0,console.error("Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release.")),H=this.props.ref,H!==void 0?H:null}function h(H,Y,O,D,T,d){var q=O.ref;return H={$$typeof:p,type:H,key:Y,props:O,_owner:D},(q!==void 0?q:null)!==null?Object.defineProperty(H,"ref",{enumerable:!1,get:b}):Object.defineProperty(H,"ref",{enumerable:!1,value:null}),H._store={},Object.defineProperty(H._store,"validated",{configurable:!1,enumerable:!1,writable:!0,value:0}),Object.defineProperty(H,"_debugInfo",{configurable:!1,enumerable:!1,writable:!0,value:null}),Object.defineProperty(H,"_debugStack",{configurable:!1,enumerable:!1,writable:!0,value:T}),Object.defineProperty(H,"_debugTask",{configurable:!1,enumerable:!1,writable:!0,value:d}),Object.freeze&&(Object.freeze(H.props),Object.freeze(H)),H}function e(H,Y,O,D,T,d){var q=Y.children;if(q!==void 0)if(D)if(S(q)){for(D=0;D<q.length;D++)c(q[D]);Object.freeze&&Object.freeze(q)}else console.error("React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead.");else c(q);if(g.call(Y,"key")){q=W(H);var E=Object.keys(Y).filter(function(HB){return HB!=="key"});D=0<E.length?"{key: someKey, "+E.join(": ..., ")+": ...}":"{key: someKey}",s[q+D]||(E=0<E.length?"{"+E.join(": ..., ")+": ...}":"{}",console.error(`A props object containing a "key" prop is being spread into JSX:
14
+ let props = %s;
15
+ <%s {...props} />
16
+ React keys must be passed directly to JSX without using spread:
17
+ let props = %s;
18
+ <%s key={someKey} {...props} />`,D,q,E,q),s[q+D]=!0)}if(q=null,O!==void 0&&(w(O),q=""+O),A(Y)&&(w(Y.key),q=""+Y.key),"key"in Y){O={};for(var N in Y)N!=="key"&&(O[N]=Y[N])}else O=Y;return q&&x(O,typeof H==="function"?H.displayName||H.name||"Unknown":H),h(H,q,O,C(),T,d)}function c(H){i(H)?H._store&&(H._store.validated=1):typeof H==="object"&&H!==null&&H.$$typeof===V&&(H._payload.status==="fulfilled"?i(H._payload.value)&&H._payload.value._store&&(H._payload.value._store.validated=1):H._store&&(H._store.validated=1))}function i(H){return typeof H==="object"&&H!==null&&H.$$typeof===p}var p=Symbol.for("react.transitional.element"),YB=Symbol.for("react.portal"),o=Symbol.for("react.fragment"),l=Symbol.for("react.strict_mode"),ZB=Symbol.for("react.profiler"),BB=Symbol.for("react.consumer"),j=Symbol.for("react.context"),zB=Symbol.for("react.forward_ref"),t=Symbol.for("react.suspense"),QB=Symbol.for("react.suspense_list"),_=Symbol.for("react.memo"),V=Symbol.for("react.lazy"),LB=Symbol.for("react.activity"),$=Symbol.for("react.client.reference"),n=f.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,g=Object.prototype.hasOwnProperty,S=Array.isArray,k=console.createTask?console.createTask:function(){return null};f={react_stack_bottom_frame:function(H){return H()}};var m,u={},a=f.react_stack_bottom_frame.bind(f,I)(),GB=k(F(I)),s={};oB.Fragment=o,oB.jsxDEV=function(H,Y,O,D){var T=1e4>n.recentlyCreatedOwnerStacks++;return e(H,Y,O,D,T?Error("react-stack-top-frame"):a,T?k(F(H)):GB)}})()});var J=JB(qB(),1);function yG(W){return J.jsxDEV("main",{className:"sign-in",children:[J.jsxDEV("div",{className:"sign-in-mark","aria-hidden":"true"},void 0,!1,void 0,this),J.jsxDEV("h1",{className:"sign-in-heading",children:W.heading},void 0,!1,void 0,this),W.error?J.jsxDEV("div",{className:"sign-in-error",role:"alert",children:W.error},void 0,!1,void 0,this):null,J.jsxDEV("form",{method:"post",action:W.action,className:"sign-in-box",children:[Object.entries(W.fields).map(([P,w])=>J.jsxDEV("input",{type:"hidden",name:P,value:w},P,!1,void 0,this)),J.jsxDEV("label",{htmlFor:W.account.name,children:W.account.label},void 0,!1,void 0,this),J.jsxDEV("input",{id:W.account.name,name:W.account.name,type:"text",autoComplete:"username",defaultValue:W.account.value??""},void 0,!1,void 0,this),J.jsxDEV("label",{htmlFor:W.password.name,children:W.password.label},void 0,!1,void 0,this),J.jsxDEV("input",{id:W.password.name,name:W.password.name,type:"password",autoComplete:"current-password"},void 0,!1,void 0,this),J.jsxDEV("button",{type:"submit",className:"sign-in-submit",children:W.submit},void 0,!1,void 0,this)]},void 0,!0,void 0,this)]},void 0,!0,void 0,this)}function cG(W){return J.jsxDEV("main",{className:"sign-in",children:[J.jsxDEV("div",{className:"sign-in-mark","aria-hidden":"true"},void 0,!1,void 0,this),J.jsxDEV("h1",{className:"sign-in-heading",children:W.heading},void 0,!1,void 0,this),W.error?J.jsxDEV("div",{className:"sign-in-error",role:"alert",children:W.error},void 0,!1,void 0,this):null,J.jsxDEV("form",{method:"post",action:W.action,className:"sign-in-box",children:[Object.entries(W.fields).map(([P,w])=>J.jsxDEV("input",{type:"hidden",name:P,value:w},P,!1,void 0,this)),J.jsxDEV("label",{htmlFor:W.code.name,children:W.code.label},void 0,!1,void 0,this),J.jsxDEV("input",{id:W.code.name,name:W.code.name,type:"text",inputMode:"numeric",autoComplete:"one-time-code"},void 0,!1,void 0,this),J.jsxDEV("p",{className:"sign-in-hint",children:W.hint},void 0,!1,void 0,this),J.jsxDEV("button",{type:"submit",className:"sign-in-submit",children:W.submit},void 0,!1,void 0,this)]},void 0,!0,void 0,this)]},void 0,!0,void 0,this)}var iG=`
19
+ body { margin: 0; font: 14px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif; }
20
+ .sign-in { max-width: 340px; margin: 0 auto; padding: 32px 16px; }
21
+ .sign-in-mark { width: 48px; height: 48px; border-radius: 50%; margin: 0 auto 16px; }
22
+ .sign-in-heading { text-align: center; font-weight: 300; font-size: 24px; margin: 0 0 16px; }
23
+ .sign-in-error { border: 1px solid; border-radius: 6px; padding: 12px 16px; margin-bottom: 16px; }
24
+ .sign-in-box { display: flex; flex-direction: column; gap: 8px; border: 1px solid; border-radius: 6px; padding: 16px; }
25
+ .sign-in-box input[type=text], .sign-in-box input[type=password] { font: inherit; padding: 5px 12px; border: 1px solid; border-radius: 6px; margin-bottom: 8px; }
26
+ .sign-in-hint { margin: 0 0 8px; font-size: 12px; }
27
+ .sign-in-submit { font: inherit; font-weight: 500; padding: 6px 16px; border-radius: 6px; border: 1px solid; cursor: pointer; }
28
+ `;function pG(W,P){for(let w of(W.headers.get("cookie")??"").split(";")){let F=w.indexOf("=");if(F>0&&w.slice(0,F).trim()===P)return decodeURIComponent(w.slice(F+1).trim())}return}export{pG as cookieOf,cG as SignInCode,yG as SignIn,iG as SIGN_IN_CSS};
@@ -0,0 +1,43 @@
1
+ export type SignInProps = {
2
+ /** the page's heading, the vendor's words ("Sign in to GitHub") */
3
+ heading: string;
4
+ /** where the form posts, and the hidden fields the vendor's form carries (where to return to) */
5
+ action: string;
6
+ fields: Record<string, string>;
7
+ /** the account field and the password field: their names, labels and (the account's) prefilled value */
8
+ account: {
9
+ name: string;
10
+ label: string;
11
+ value?: string;
12
+ };
13
+ password: {
14
+ name: string;
15
+ label: string;
16
+ };
17
+ submit: string;
18
+ /** the vendor's message for a refused attempt ("Incorrect username or password.") */
19
+ error?: string;
20
+ };
21
+ export declare function SignIn(props: SignInProps): import("react").JSX.Element;
22
+ /** A sign-in's second step, for a person who has two-factor authentication: after the right password the vendor asks
23
+ * for the code their authenticator shows, on a page of its own. It lays out as the sign-in does (SIGN_IN_CSS). */
24
+ export type SignInCodeProps = {
25
+ /** the page's heading and the line under it, the vendor's words */
26
+ heading: string;
27
+ hint: string;
28
+ action: string;
29
+ fields: Record<string, string>;
30
+ /** the code field: its name and label */
31
+ code: {
32
+ name: string;
33
+ label: string;
34
+ };
35
+ submit: string;
36
+ /** the vendor's message for a refused code */
37
+ error?: string;
38
+ };
39
+ export declare function SignInCode(props: SignInCodeProps): import("react").JSX.Element;
40
+ /** The sign-in page's layout; a pack's skin sets the colours and type over it. */
41
+ export declare const SIGN_IN_CSS = "\nbody { margin: 0; font: 14px/1.5 system-ui, -apple-system, \"Segoe UI\", sans-serif; }\n.sign-in { max-width: 340px; margin: 0 auto; padding: 32px 16px; }\n.sign-in-mark { width: 48px; height: 48px; border-radius: 50%; margin: 0 auto 16px; }\n.sign-in-heading { text-align: center; font-weight: 300; font-size: 24px; margin: 0 0 16px; }\n.sign-in-error { border: 1px solid; border-radius: 6px; padding: 12px 16px; margin-bottom: 16px; }\n.sign-in-box { display: flex; flex-direction: column; gap: 8px; border: 1px solid; border-radius: 6px; padding: 16px; }\n.sign-in-box input[type=text], .sign-in-box input[type=password] { font: inherit; padding: 5px 12px; border: 1px solid; border-radius: 6px; margin-bottom: 8px; }\n.sign-in-hint { margin: 0 0 8px; font-size: 12px; }\n.sign-in-submit { font: inherit; font-weight: 500; padding: 6px 16px; border-radius: 6px; border: 1px solid; cursor: pointer; }\n";
42
+ /** The value of the cookie `name` the request carries, as a browser sends it back. */
43
+ export declare function cookieOf(request: Request, name: string): string | undefined;
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export function SignIn(props) {
3
+ return (_jsxs("main", { className: "sign-in", children: [_jsx("div", { className: "sign-in-mark", "aria-hidden": "true" }), _jsx("h1", { className: "sign-in-heading", children: props.heading }), props.error ? _jsx("div", { className: "sign-in-error", role: "alert", children: props.error }) : null, _jsxs("form", { method: "post", action: props.action, className: "sign-in-box", children: [Object.entries(props.fields).map(([name, value]) => _jsx("input", { type: "hidden", name: name, value: value }, name)), _jsx("label", { htmlFor: props.account.name, children: props.account.label }), _jsx("input", { id: props.account.name, name: props.account.name, type: "text", autoComplete: "username", defaultValue: props.account.value ?? '' }), _jsx("label", { htmlFor: props.password.name, children: props.password.label }), _jsx("input", { id: props.password.name, name: props.password.name, type: "password", autoComplete: "current-password" }), _jsx("button", { type: "submit", className: "sign-in-submit", children: props.submit })] })] }));
4
+ }
5
+ export function SignInCode(props) {
6
+ return (_jsxs("main", { className: "sign-in", children: [_jsx("div", { className: "sign-in-mark", "aria-hidden": "true" }), _jsx("h1", { className: "sign-in-heading", children: props.heading }), props.error ? _jsx("div", { className: "sign-in-error", role: "alert", children: props.error }) : null, _jsxs("form", { method: "post", action: props.action, className: "sign-in-box", children: [Object.entries(props.fields).map(([name, value]) => _jsx("input", { type: "hidden", name: name, value: value }, name)), _jsx("label", { htmlFor: props.code.name, children: props.code.label }), _jsx("input", { id: props.code.name, name: props.code.name, type: "text", inputMode: "numeric", autoComplete: "one-time-code" }), _jsx("p", { className: "sign-in-hint", children: props.hint }), _jsx("button", { type: "submit", className: "sign-in-submit", children: props.submit })] })] }));
7
+ }
8
+ /** The sign-in page's layout; a pack's skin sets the colours and type over it. */
9
+ export const SIGN_IN_CSS = `
10
+ body { margin: 0; font: 14px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif; }
11
+ .sign-in { max-width: 340px; margin: 0 auto; padding: 32px 16px; }
12
+ .sign-in-mark { width: 48px; height: 48px; border-radius: 50%; margin: 0 auto 16px; }
13
+ .sign-in-heading { text-align: center; font-weight: 300; font-size: 24px; margin: 0 0 16px; }
14
+ .sign-in-error { border: 1px solid; border-radius: 6px; padding: 12px 16px; margin-bottom: 16px; }
15
+ .sign-in-box { display: flex; flex-direction: column; gap: 8px; border: 1px solid; border-radius: 6px; padding: 16px; }
16
+ .sign-in-box input[type=text], .sign-in-box input[type=password] { font: inherit; padding: 5px 12px; border: 1px solid; border-radius: 6px; margin-bottom: 8px; }
17
+ .sign-in-hint { margin: 0 0 8px; font-size: 12px; }
18
+ .sign-in-submit { font: inherit; font-weight: 500; padding: 6px 16px; border-radius: 6px; border: 1px solid; cursor: pointer; }
19
+ `;
20
+ /** The value of the cookie `name` the request carries, as a browser sends it back. */
21
+ export function cookieOf(request, name) {
22
+ for (const part of (request.headers.get('cookie') ?? '').split(';')) {
23
+ const at = part.indexOf('=');
24
+ if (at > 0 && part.slice(0, at).trim() === name)
25
+ return decodeURIComponent(part.slice(at + 1).trim());
26
+ }
27
+ return undefined;
28
+ }
@@ -0,0 +1,90 @@
1
+ // A SIGN-IN PAGE — the door every vendor with pages puts before them (docs/contributing/architecture.md, "Who is on a
2
+ // screen"): a person names their account and gives their password, and the vendor's session cookie names them to every
3
+ // page after. The page is a plain form rendered on the server, so submitting it posts and the vendor's redirect follows
4
+ // with no script. A pack gives the vendor's words, its field names and where the form posts, and its skin the look; the
5
+ // controls carry the vendor's labels so a test that finds them by label finds them.
6
+ export type SignInProps = {
7
+ /** the page's heading, the vendor's words ("Sign in to GitHub") */
8
+ heading: string;
9
+ /** where the form posts, and the hidden fields the vendor's form carries (where to return to) */
10
+ action: string;
11
+ fields: Record<string, string>;
12
+ /** the account field and the password field: their names, labels and (the account's) prefilled value */
13
+ account: { name: string; label: string; value?: string };
14
+ password: { name: string; label: string };
15
+ submit: string;
16
+ /** the vendor's message for a refused attempt ("Incorrect username or password.") */
17
+ error?: string;
18
+ };
19
+
20
+ export function SignIn(props: SignInProps) {
21
+ return (
22
+ <main className="sign-in">
23
+ <div className="sign-in-mark" aria-hidden="true" />
24
+ <h1 className="sign-in-heading">{props.heading}</h1>
25
+ {props.error ? <div className="sign-in-error" role="alert">{props.error}</div> : null}
26
+ <form method="post" action={props.action} className="sign-in-box">
27
+ {Object.entries(props.fields).map(([name, value]) => <input key={name} type="hidden" name={name} value={value} />)}
28
+ <label htmlFor={props.account.name}>{props.account.label}</label>
29
+ <input id={props.account.name} name={props.account.name} type="text" autoComplete="username" defaultValue={props.account.value ?? ''} />
30
+ <label htmlFor={props.password.name}>{props.password.label}</label>
31
+ <input id={props.password.name} name={props.password.name} type="password" autoComplete="current-password" />
32
+ <button type="submit" className="sign-in-submit">{props.submit}</button>
33
+ </form>
34
+ </main>
35
+ );
36
+ }
37
+
38
+ /** A sign-in's second step, for a person who has two-factor authentication: after the right password the vendor asks
39
+ * for the code their authenticator shows, on a page of its own. It lays out as the sign-in does (SIGN_IN_CSS). */
40
+ export type SignInCodeProps = {
41
+ /** the page's heading and the line under it, the vendor's words */
42
+ heading: string;
43
+ hint: string;
44
+ action: string;
45
+ fields: Record<string, string>;
46
+ /** the code field: its name and label */
47
+ code: { name: string; label: string };
48
+ submit: string;
49
+ /** the vendor's message for a refused code */
50
+ error?: string;
51
+ };
52
+
53
+ export function SignInCode(props: SignInCodeProps) {
54
+ return (
55
+ <main className="sign-in">
56
+ <div className="sign-in-mark" aria-hidden="true" />
57
+ <h1 className="sign-in-heading">{props.heading}</h1>
58
+ {props.error ? <div className="sign-in-error" role="alert">{props.error}</div> : null}
59
+ <form method="post" action={props.action} className="sign-in-box">
60
+ {Object.entries(props.fields).map(([name, value]) => <input key={name} type="hidden" name={name} value={value} />)}
61
+ <label htmlFor={props.code.name}>{props.code.label}</label>
62
+ <input id={props.code.name} name={props.code.name} type="text" inputMode="numeric" autoComplete="one-time-code" />
63
+ <p className="sign-in-hint">{props.hint}</p>
64
+ <button type="submit" className="sign-in-submit">{props.submit}</button>
65
+ </form>
66
+ </main>
67
+ );
68
+ }
69
+
70
+ /** The sign-in page's layout; a pack's skin sets the colours and type over it. */
71
+ export const SIGN_IN_CSS = `
72
+ body { margin: 0; font: 14px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif; }
73
+ .sign-in { max-width: 340px; margin: 0 auto; padding: 32px 16px; }
74
+ .sign-in-mark { width: 48px; height: 48px; border-radius: 50%; margin: 0 auto 16px; }
75
+ .sign-in-heading { text-align: center; font-weight: 300; font-size: 24px; margin: 0 0 16px; }
76
+ .sign-in-error { border: 1px solid; border-radius: 6px; padding: 12px 16px; margin-bottom: 16px; }
77
+ .sign-in-box { display: flex; flex-direction: column; gap: 8px; border: 1px solid; border-radius: 6px; padding: 16px; }
78
+ .sign-in-box input[type=text], .sign-in-box input[type=password] { font: inherit; padding: 5px 12px; border: 1px solid; border-radius: 6px; margin-bottom: 8px; }
79
+ .sign-in-hint { margin: 0 0 8px; font-size: 12px; }
80
+ .sign-in-submit { font: inherit; font-weight: 500; padding: 6px 16px; border-radius: 6px; border: 1px solid; cursor: pointer; }
81
+ `;
82
+
83
+ /** The value of the cookie `name` the request carries, as a browser sends it back. */
84
+ export function cookieOf(request: Request, name: string): string | undefined {
85
+ for (const part of (request.headers.get('cookie') ?? '').split(';')) {
86
+ const at = part.indexOf('=');
87
+ if (at > 0 && part.slice(0, at).trim() === name) return decodeURIComponent(part.slice(at + 1).trim());
88
+ }
89
+ return undefined;
90
+ }
@@ -0,0 +1,33 @@
1
+ import type { ReactElement } from 'react';
2
+ export type TwinUiOptions = {
3
+ title: string;
4
+ /** the pack's client entry (a .tsx that calls mountTwinUi), resolved when first served */
5
+ clientEntry: () => string;
6
+ /** the pack's own stylesheet: its skin over the kit's */
7
+ css?: () => string;
8
+ /** the pack's fetch: every request that is not the UI's own is the vendor API */
9
+ api: (request: Request) => Promise<Response>;
10
+ };
11
+ export declare function twinUiHtml(title: string): string;
12
+ /** The UI's routes (the shell, the client, the stylesheets) over the pack's API. */
13
+ export declare function twinUiFetch(options: TwinUiOptions): (request: Request) => Promise<Response>;
14
+ /** The UI on its own loopback port. Loopback-specific, so the port the kernel hands out is free on 127.0.0.1. */
15
+ export declare function serveTwinUi(options: TwinUiOptions & {
16
+ port?: number;
17
+ }): Promise<{
18
+ port: number;
19
+ stop: () => void;
20
+ }>;
21
+ /** A hosted flow's page (docs/contributing/architecture.md, "Screens"): rendered on the server at the
22
+ * vendor's own host and path, its styles inline, so it needs no asset routes beside it. */
23
+ export declare function flowPage(options: {
24
+ title: string;
25
+ css: string[];
26
+ body: ReactElement;
27
+ status?: number;
28
+ headers?: Record<string, string>;
29
+ }): Response;
30
+ export { Consent, CONSENT_CSS, type ConsentPermission, type ConsentProps } from '../client/consent.js';
31
+ export { FORMAT_SCRIPT, Payment, PAYMENT_CSS, type PaymentField, type PaymentProps } from '../client/payment.js';
32
+ export { Portal, PORTAL_CSS, type PortalAction, type PortalForm, type PortalItem, type PortalProps, type PortalSection } from '../client/portal.js';
33
+ export { cookieOf, SignIn, SignInCode, SIGN_IN_CSS, type SignInCodeProps, type SignInProps } from '../client/sign-in.js';
@@ -0,0 +1,56 @@
1
+ import { renderToStaticMarkup } from 'react-dom/server';
2
+ import { brandTokensResponse, bundleClient, fileResponse, serveHttp } from '@volter/world-core';
3
+ const KIT_CSS = () => new URL('../client/kit.css', import.meta.url).pathname;
4
+ /** The brand's tokens and faces the kit's roles read (company decision 0018), fetched at build into client/brand/ (gitignored). */
5
+ const BRAND_DIR = () => new URL('../client/brand', import.meta.url).pathname;
6
+ export function twinUiHtml(title) {
7
+ const safe = title.replace(/[<>&"]/g, '');
8
+ return `<!doctype html>
9
+ <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
10
+ <base href="/"><title>${safe}</title><link rel="stylesheet" href="assets/brand/tokens.css"><link rel="stylesheet" href="assets/kit.css"><link rel="stylesheet" href="assets/styles.css"></head>
11
+ <body><div id="root"></div><script type="module" src="assets/app.js"></script></body></html>`;
12
+ }
13
+ /** The UI's routes (the shell, the client, the stylesheets) over the pack's API. */
14
+ export function twinUiFetch(options) {
15
+ return async (request) => {
16
+ const url = new URL(request.url);
17
+ if (request.method === 'GET') {
18
+ if (url.pathname === '/' || url.pathname === '')
19
+ return new Response(twinUiHtml(options.title), { headers: { 'content-type': 'text/html; charset=utf-8' } });
20
+ if (url.pathname === '/assets/app.js') {
21
+ try {
22
+ return new Response(await bundleClient(options.clientEntry()), { headers: { 'content-type': 'text/javascript; charset=utf-8' } });
23
+ }
24
+ catch (error) {
25
+ return new Response(String(error), { status: 500 });
26
+ }
27
+ }
28
+ if (url.pathname.startsWith('/assets/brand/'))
29
+ return brandTokensResponse(BRAND_DIR(), url.pathname.slice('/assets/brand/'.length));
30
+ if (url.pathname === '/assets/kit.css')
31
+ return fileResponse(KIT_CSS(), { headers: { 'content-type': 'text/css; charset=utf-8' } });
32
+ if (url.pathname === '/assets/styles.css')
33
+ return options.css ? fileResponse(options.css(), { headers: { 'content-type': 'text/css; charset=utf-8' } }) : new Response('', { headers: { 'content-type': 'text/css; charset=utf-8' } });
34
+ }
35
+ return options.api(request);
36
+ };
37
+ }
38
+ /** The UI on its own loopback port. Loopback-specific, so the port the kernel hands out is free on 127.0.0.1. */
39
+ export async function serveTwinUi(options) {
40
+ const server = await serveHttp({ hostname: '127.0.0.1', port: options.port ?? 0, idleTimeout: 60, fetch: twinUiFetch(options) });
41
+ return { port: server.port ?? options.port ?? 0, stop: () => server.stop(true) };
42
+ }
43
+ /** A hosted flow's page (docs/contributing/architecture.md, "Screens"): rendered on the server at the
44
+ * vendor's own host and path, its styles inline, so it needs no asset routes beside it. */
45
+ export function flowPage(options) {
46
+ const safe = options.title.replace(/[<>&"]/g, '');
47
+ const html = `<!doctype html>
48
+ <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${safe}</title>
49
+ <style>${options.css.join('\n')}</style></head>
50
+ <body>${renderToStaticMarkup(options.body)}</body></html>`;
51
+ return new Response(html, { status: options.status ?? 200, headers: { 'content-type': 'text/html; charset=utf-8', ...(options.headers ?? {}) } });
52
+ }
53
+ export { Consent, CONSENT_CSS } from "../client/consent.js";
54
+ export { FORMAT_SCRIPT, Payment, PAYMENT_CSS } from "../client/payment.js";
55
+ export { Portal, PORTAL_CSS } from "../client/portal.js";
56
+ export { cookieOf, SignIn, SignInCode, SIGN_IN_CSS } from "../client/sign-in.js";
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@volter/world-ui",
3
+ "version": "0.1.0",
4
+ "description": "Shared pieces and serving for derived packs' screens (hosted flows and workspaces), through each pack's own API; see docs/contributing/architecture.md, Screens.",
5
+ "license": "Apache-2.0",
6
+ "author": "Volter (https://github.com/volter-ai)",
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
10
+ "type": "module",
11
+ "scripts": {
12
+ "build": "node ../../scripts/publish/build.mjs",
13
+ "prepack": "bun ../../scripts/brand-tokens.ts client/brand && node ../../scripts/publish/prepare-publish.mjs prepack",
14
+ "postpack": "node ../../scripts/publish/prepare-publish.mjs postpack"
15
+ },
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/src/index.d.ts",
19
+ "default": "./dist/src/index.js"
20
+ },
21
+ "./kit": "./client/kit.tsx"
22
+ },
23
+ "files": [
24
+ "src",
25
+ "client",
26
+ "LICENSE",
27
+ "!**/*.test.ts",
28
+ "!**/*.test.tsx",
29
+ "dist"
30
+ ],
31
+ "peerDependencies": {
32
+ "@volter/world-core": "2.0.0",
33
+ "react": "^19.2.7",
34
+ "react-dom": "^19.2.7"
35
+ },
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/volter-ai/twin.git",
39
+ "directory": "packages/world-ui"
40
+ },
41
+ "homepage": "https://github.com/volter-ai/twin/tree/main/packages/world-ui#readme",
42
+ "engines": {
43
+ "node": ">=22.3"
44
+ }
45
+ }
package/src/index.ts ADDED
@@ -0,0 +1,72 @@
1
+ // THE TWIN UI SERVER — what every pack's vendor UI needs around its own client: the HTML shell, the
2
+ // bundled client, its stylesheets, and the pack's own API behind the same origin, so the UI reads and
3
+ // writes exactly what an SDK does (docs/contributing/architecture.md, "Protocol 3"). A pack gives
4
+ // its title, its client entry and stylesheet, and its fetch; nothing here knows a vendor.
5
+ import type { ReactElement } from 'react';
6
+ import { renderToStaticMarkup } from 'react-dom/server';
7
+ import { brandTokensResponse, bundleClient, fileResponse, serveHttp } from '@volter/world-core';
8
+
9
+ export type TwinUiOptions = {
10
+ title: string;
11
+ /** the pack's client entry (a .tsx that calls mountTwinUi), resolved when first served */
12
+ clientEntry: () => string;
13
+ /** the pack's own stylesheet: its skin over the kit's */
14
+ css?: () => string;
15
+ /** the pack's fetch: every request that is not the UI's own is the vendor API */
16
+ api: (request: Request) => Promise<Response>;
17
+ };
18
+
19
+ const KIT_CSS = (): string => new URL('../client/kit.css', import.meta.url).pathname;
20
+ /** The brand's tokens and faces the kit's roles read (company decision 0018), fetched at build into client/brand/ (gitignored). */
21
+ const BRAND_DIR = (): string => new URL('../client/brand', import.meta.url).pathname;
22
+
23
+ export function twinUiHtml(title: string): string {
24
+ const safe = title.replace(/[<>&"]/g, '');
25
+ return `<!doctype html>
26
+ <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
27
+ <base href="/"><title>${safe}</title><link rel="stylesheet" href="assets/brand/tokens.css"><link rel="stylesheet" href="assets/kit.css"><link rel="stylesheet" href="assets/styles.css"></head>
28
+ <body><div id="root"></div><script type="module" src="assets/app.js"></script></body></html>`;
29
+ }
30
+
31
+ /** The UI's routes (the shell, the client, the stylesheets) over the pack's API. */
32
+ export function twinUiFetch(options: TwinUiOptions): (request: Request) => Promise<Response> {
33
+ return async (request) => {
34
+ const url = new URL(request.url);
35
+ if (request.method === 'GET') {
36
+ if (url.pathname === '/' || url.pathname === '') return new Response(twinUiHtml(options.title), { headers: { 'content-type': 'text/html; charset=utf-8' } });
37
+ if (url.pathname === '/assets/app.js') {
38
+ try {
39
+ return new Response(await bundleClient(options.clientEntry()), { headers: { 'content-type': 'text/javascript; charset=utf-8' } });
40
+ } catch (error) {
41
+ return new Response(String(error), { status: 500 });
42
+ }
43
+ }
44
+ if (url.pathname.startsWith('/assets/brand/')) return brandTokensResponse(BRAND_DIR(), url.pathname.slice('/assets/brand/'.length));
45
+ if (url.pathname === '/assets/kit.css') return fileResponse(KIT_CSS(), { headers: { 'content-type': 'text/css; charset=utf-8' } });
46
+ if (url.pathname === '/assets/styles.css') return options.css ? fileResponse(options.css(), { headers: { 'content-type': 'text/css; charset=utf-8' } }) : new Response('', { headers: { 'content-type': 'text/css; charset=utf-8' } });
47
+ }
48
+ return options.api(request);
49
+ };
50
+ }
51
+
52
+ /** The UI on its own loopback port. Loopback-specific, so the port the kernel hands out is free on 127.0.0.1. */
53
+ export async function serveTwinUi(options: TwinUiOptions & { port?: number }): Promise<{ port: number; stop: () => void }> {
54
+ const server = await serveHttp({ hostname: '127.0.0.1', port: options.port ?? 0, idleTimeout: 60, fetch: twinUiFetch(options) });
55
+ return { port: server.port ?? options.port ?? 0, stop: () => server.stop(true) };
56
+ }
57
+
58
+ /** A hosted flow's page (docs/contributing/architecture.md, "Screens"): rendered on the server at the
59
+ * vendor's own host and path, its styles inline, so it needs no asset routes beside it. */
60
+ export function flowPage(options: { title: string; css: string[]; body: ReactElement; status?: number; headers?: Record<string, string> }): Response {
61
+ const safe = options.title.replace(/[<>&"]/g, '');
62
+ const html = `<!doctype html>
63
+ <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${safe}</title>
64
+ <style>${options.css.join('\n')}</style></head>
65
+ <body>${renderToStaticMarkup(options.body)}</body></html>`;
66
+ return new Response(html, { status: options.status ?? 200, headers: { 'content-type': 'text/html; charset=utf-8', ...(options.headers ?? {}) } });
67
+ }
68
+
69
+ export { Consent, CONSENT_CSS, type ConsentPermission, type ConsentProps } from '../client/consent.tsx';
70
+ export { FORMAT_SCRIPT, Payment, PAYMENT_CSS, type PaymentField, type PaymentProps } from '../client/payment.tsx';
71
+ export { Portal, PORTAL_CSS, type PortalAction, type PortalForm, type PortalItem, type PortalProps, type PortalSection } from '../client/portal.tsx';
72
+ export { cookieOf, SignIn, SignInCode, SIGN_IN_CSS, type SignInCodeProps, type SignInProps } from '../client/sign-in.tsx';