feat(landing): self-serve registration page via Drasl API (B1)
Add a static /register page (en/es/eu) that calls the Drasl REST API v2 directly from the browser: register -> login -> optional skin upload, all in the pixel design. Guests never touch Drasl's own web UI. Drasl config gains the pieces this needs: - CORSAllowOrigins scoped to the apex (the API has no CORS until set; never "*"). - [RateLimit] for the now public-facing anonymous POST /users. - [RegistrationNewPlayer] with RequireInvite driven by a new REGISTRATION_MODE (invite|open) .env flag. REGISTRATION_MODE has two consumers from one key: render-config.sh derives the TOML boolean for Drasl (drasl is TOML-only, no env), and the landing build reads it to show/hide the invite-code field. render-config.sh halts on any value other than invite/open. Security verified against drasl source: anonymous POST /users cannot set privileged fields (isAdmin/isLocked/chosenUuid/maxPlayerCount are gated on callerIsAdmin in CreateUser), so browser-direct registration is safe. Docs: plan/16-landing-registration.md captures the design + the B1 vs fork decision; build-order, deploy, and the deploy skill wire REGISTRATION_MODE and the landing-rebuild requirement (www/ is gitignored, not updated by a git pull). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,12 @@ export const LANGS: Lang[] = ["en", "es", "eu"];
|
||||
export const LANG_LABEL: Record<Lang, string> = { en: "EN", es: "ES", eu: "EU" };
|
||||
// URL path per locale (en is default, served at root).
|
||||
export const LANG_PATH: Record<Lang, string> = { en: "/", es: "/es/", eu: "/eu/" };
|
||||
// Register page path per locale (mirrors LANG_PATH: en at /register).
|
||||
export const LANG_REGISTER_PATH: Record<Lang, string> = {
|
||||
en: "/register",
|
||||
es: "/es/register",
|
||||
eu: "/eu/register",
|
||||
};
|
||||
|
||||
export interface UI {
|
||||
htmlLang: string;
|
||||
@@ -65,6 +71,39 @@ export interface UI {
|
||||
};
|
||||
};
|
||||
footer: { join: string; disc: string };
|
||||
register: {
|
||||
navHome: string; // "← Home" link in nav
|
||||
eyebrow: string;
|
||||
title: string;
|
||||
lead: string;
|
||||
usernameLabel: string;
|
||||
usernamePlaceholder: string;
|
||||
passwordLabel: string;
|
||||
passwordPlaceholder: string;
|
||||
inviteLabel: string;
|
||||
invitePlaceholder: string;
|
||||
inviteHint: string; // where to get a code
|
||||
submit: string;
|
||||
submitting: string;
|
||||
haveAccount: string; // "Already have an account?"
|
||||
loginLink: string; // links to the Drasl web UI
|
||||
successTitle: string;
|
||||
successBody: string; // {name} = player name
|
||||
uuidLabel: string;
|
||||
skinTitle: string;
|
||||
skinLead: string;
|
||||
skinChoose: string;
|
||||
modelLabel: string;
|
||||
modelClassic: string;
|
||||
modelSlim: string;
|
||||
skinUpload: string;
|
||||
skinUploading: string;
|
||||
skinOk: string;
|
||||
finish: string; // proceed to How to Join
|
||||
errFields: string;
|
||||
errSkinType: string;
|
||||
errNetwork: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const ui: Record<Lang, UI> = {
|
||||
@@ -152,6 +191,39 @@ export const ui: Record<Lang, UI> = {
|
||||
join: "How to Join",
|
||||
disc: "Not affiliated with Mojang or Microsoft. Minecraft is a trademark of Mojang AB.",
|
||||
},
|
||||
register: {
|
||||
navHome: "← Home",
|
||||
eyebrow: "Account",
|
||||
title: "Create your Ulicraft account.",
|
||||
lead: "One username and password for the server, your skin, and your cape. Takes a minute.",
|
||||
usernameLabel: "Username",
|
||||
usernamePlaceholder: "your in-game name",
|
||||
passwordLabel: "Password",
|
||||
passwordPlaceholder: "choose a password",
|
||||
inviteLabel: "Invite code",
|
||||
invitePlaceholder: "paste your invite code",
|
||||
inviteHint: "Ask an admin for an invite code.",
|
||||
submit: "Create account",
|
||||
submitting: "Creating…",
|
||||
haveAccount: "Already have an account?",
|
||||
loginLink: "Manage it here",
|
||||
successTitle: "Account created.",
|
||||
successBody: "Welcome, {name}. You can set a skin now, or head straight to How to Join.",
|
||||
uuidLabel: "Player UUID",
|
||||
skinTitle: "Set your skin (optional)",
|
||||
skinLead: "Upload a Minecraft skin PNG. You can always change it later in the account page.",
|
||||
skinChoose: "Choose PNG…",
|
||||
modelLabel: "Model",
|
||||
modelClassic: "Classic",
|
||||
modelSlim: "Slim",
|
||||
skinUpload: "Upload skin",
|
||||
skinUploading: "Uploading…",
|
||||
skinOk: "Skin set!",
|
||||
finish: "Continue to How to Join",
|
||||
errFields: "Fill in every field.",
|
||||
errSkinType: "Pick a PNG image.",
|
||||
errNetwork: "Network error — try again.",
|
||||
},
|
||||
},
|
||||
|
||||
es: {
|
||||
@@ -238,6 +310,39 @@ export const ui: Record<Lang, UI> = {
|
||||
join: "Cómo entrar",
|
||||
disc: "No afiliado a Mojang ni Microsoft. Minecraft es una marca de Mojang AB.",
|
||||
},
|
||||
register: {
|
||||
navHome: "← Inicio",
|
||||
eyebrow: "Cuenta",
|
||||
title: "Crea tu cuenta de Ulicraft.",
|
||||
lead: "Un usuario y contraseña para el servidor, tu skin y tu capa. Cosa de un minuto.",
|
||||
usernameLabel: "Usuario",
|
||||
usernamePlaceholder: "tu nombre en el juego",
|
||||
passwordLabel: "Contraseña",
|
||||
passwordPlaceholder: "elige una contraseña",
|
||||
inviteLabel: "Código de invitación",
|
||||
invitePlaceholder: "pega tu código de invitación",
|
||||
inviteHint: "Pide un código de invitación a un admin.",
|
||||
submit: "Crear cuenta",
|
||||
submitting: "Creando…",
|
||||
haveAccount: "¿Ya tienes cuenta?",
|
||||
loginLink: "Gestiónala aquí",
|
||||
successTitle: "Cuenta creada.",
|
||||
successBody: "Bienvenido, {name}. Puedes poner una skin ahora o ir directo a Cómo entrar.",
|
||||
uuidLabel: "UUID del jugador",
|
||||
skinTitle: "Pon tu skin (opcional)",
|
||||
skinLead: "Sube un PNG de skin de Minecraft. Siempre puedes cambiarla luego en tu cuenta.",
|
||||
skinChoose: "Elegir PNG…",
|
||||
modelLabel: "Modelo",
|
||||
modelClassic: "Clásico",
|
||||
modelSlim: "Fino",
|
||||
skinUpload: "Subir skin",
|
||||
skinUploading: "Subiendo…",
|
||||
skinOk: "¡Skin puesta!",
|
||||
finish: "Continuar a Cómo entrar",
|
||||
errFields: "Rellena todos los campos.",
|
||||
errSkinType: "Elige una imagen PNG.",
|
||||
errNetwork: "Error de red: inténtalo de nuevo.",
|
||||
},
|
||||
},
|
||||
|
||||
eu: {
|
||||
@@ -324,5 +429,38 @@ export const ui: Record<Lang, UI> = {
|
||||
join: "Nola sartu",
|
||||
disc: "Ez dago Mojang edo Microsoft-ekin lotuta. Minecraft Mojang AB-ren marka da.",
|
||||
},
|
||||
register: {
|
||||
navHome: "← Hasiera",
|
||||
eyebrow: "Kontua",
|
||||
title: "Sortu zure Ulicraft kontua.",
|
||||
lead: "Erabiltzaile eta pasahitz bat zerbitzarirako, zure azalerako eta kaparako. Minutu batean.",
|
||||
usernameLabel: "Erabiltzailea",
|
||||
usernamePlaceholder: "zure jokoko izena",
|
||||
passwordLabel: "Pasahitza",
|
||||
passwordPlaceholder: "aukeratu pasahitz bat",
|
||||
inviteLabel: "Gonbidapen-kodea",
|
||||
invitePlaceholder: "itsatsi zure gonbidapen-kodea",
|
||||
inviteHint: "Eskatu gonbidapen-kode bat admin bati.",
|
||||
submit: "Sortu kontua",
|
||||
submitting: "Sortzen…",
|
||||
haveAccount: "Baduzu kontua?",
|
||||
loginLink: "Kudeatu hemen",
|
||||
successTitle: "Kontua sortuta.",
|
||||
successBody: "Ongi etorri, {name}. Azala orain jar dezakezu, edo zuzenean Nola sartu atalera joan.",
|
||||
uuidLabel: "Jokalariaren UUIDa",
|
||||
skinTitle: "Jarri zure azala (aukerakoa)",
|
||||
skinLead: "Igo Minecraft azal PNG bat. Beti alda dezakezu gero zure kontuan.",
|
||||
skinChoose: "Aukeratu PNGa…",
|
||||
modelLabel: "Eredua",
|
||||
modelClassic: "Klasikoa",
|
||||
modelSlim: "Mehea",
|
||||
skinUpload: "Igo azala",
|
||||
skinUploading: "Igotzen…",
|
||||
skinOk: "Azala jarrita!",
|
||||
finish: "Jarraitu Nola sartura",
|
||||
errFields: "Bete eremu guztiak.",
|
||||
errSkinType: "Aukeratu PNG irudi bat.",
|
||||
errNetwork: "Sare-errorea: saiatu berriro.",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user