The 35 files behind twelve public pages, seven admin views, two player views and three core-page extensions, ported onto `window.__rg`. Every one of them imports exactly the seven kit members plus `lib/format.js`, which is the finding §2.7.1 predicted and this confirms. `client/src/core.js` is the port mechanism, and unlike the server's it is a plain read: `window.__rg` is published before any module chunk evaluates, so there is no gap to defer around and a ported component keeps its ordinary import shape. `client/src/api.js` rebuilds the UO namespaces over the request primitive — same URLs, because §1.2 freezes the API surface. SPA paths changed and API paths did not. `/site/shard` is `/uo/shard`, and the admin paths lost their now-redundant `shard-` prefixes (`/admin/uo/ops`), a clean break being the only moment that is free. `shim/rg.js` becomes the single reader of the global, so the "core did not publish its dependencies" message is reachable from whichever module the bundler happens to touch first rather than from whichever one is imported first — a guarantee that used to last until someone sorted the imports. Co-Authored-By: Claude <noreply@anthropic.com>
70 lines
3.1 KiB
JavaScript
70 lines
3.1 KiB
JavaScript
import { useState } from 'react'
|
||
|
||
// Reusable "create a game account" form (its own username + password — the game
|
||
// client credentials, distinct from the website login). Calls `submit(account,
|
||
// password)` which should POST /player/shard/account; on success calls onCreated.
|
||
// Used by the player portal (self-serve) and the invite-accept page alike.
|
||
export default function CreateGameAccountForm({ submit, onCreated, compact = false }) {
|
||
const [account, setAccount] = useState('')
|
||
const [password, setPassword] = useState('')
|
||
const [busy, setBusy] = useState(false)
|
||
const [msg, setMsg] = useState('')
|
||
const [error, setError] = useState('')
|
||
|
||
async function onSubmit(e) {
|
||
e.preventDefault()
|
||
setMsg(''); setError('')
|
||
if (!/^[A-Za-z0-9][A-Za-z0-9_.-]{2,29}$/.test(account)) {
|
||
return setError('Account name must be 3–30 letters, numbers, . _ or -.')
|
||
}
|
||
if (password.length < 8) return setError('Password must be at least 8 characters.')
|
||
setBusy(true)
|
||
try {
|
||
await submit(account, password)
|
||
setMsg(`Game account “${account}” created and linked.`)
|
||
setAccount(''); setPassword('')
|
||
if (onCreated) await onCreated()
|
||
} catch (err) {
|
||
if (err.status === 409) setError('That account name is already taken.')
|
||
else if (err.status === 429) setError('The account limit for your network has been reached.')
|
||
else if (err.status === 403) setError('Game-account signup is not available right now.')
|
||
else if (err.status === 503) setError('The game server is unavailable — try again shortly.')
|
||
else setError(err.message || 'Could not create the account right now.')
|
||
} finally {
|
||
setBusy(false)
|
||
}
|
||
}
|
||
|
||
return (
|
||
<form onSubmit={onSubmit}>
|
||
{!compact && (
|
||
<p className="sans" style={{ marginTop: 0, color: 'var(--muted)', fontSize: '0.88rem', lineHeight: 1.6 }}>
|
||
Choose the username and password you’ll type into the game client. These are your
|
||
<strong style={{ color: 'var(--head)' }}> game</strong> credentials — separate from your website login.
|
||
</p>
|
||
)}
|
||
<label style={{ display: 'block', marginBottom: 14 }}>
|
||
<span className="field-label">Game account name</span>
|
||
<input
|
||
type="text" autoComplete="off" value={account}
|
||
onChange={(e) => setAccount(e.target.value)} className="input" placeholder="e.g. darrow"
|
||
/>
|
||
</label>
|
||
<label style={{ display: 'block', marginBottom: 16 }}>
|
||
<span className="field-label">Game password</span>
|
||
<input
|
||
type="password" autoComplete="new-password" value={password}
|
||
onChange={(e) => setPassword(e.target.value)} className="input"
|
||
/>
|
||
</label>
|
||
|
||
{error && <p className="sans" style={{ margin: '0 0 12px', color: '#d98b84', fontSize: '0.85rem' }}>{error}</p>}
|
||
{msg && <p className="sans" style={{ margin: '0 0 12px', color: '#7fd0a4', fontSize: '0.85rem' }}>{msg}</p>}
|
||
|
||
<button type="submit" disabled={busy} className="btn btn-primary btn-sq">
|
||
{busy ? 'Creating…' : 'Create game account'}
|
||
</button>
|
||
</form>
|
||
)
|
||
}
|