feat(profile): set a display name and greet with it on the dashboard

Settings: new Profil card with display name and Steam ID. Registration: optional display name. The dashboard falls back to the email prefix when no display name is set.
This commit is contained in:
lobo committed 2026-09-24 10:45:56 +02:00
1 parent 6773040d59
commit 7b676f0a1e
7 files changed
+90 -13

No files matched your search

+13
View File
@@ -22,6 +22,17 @@ const steamIdField = z
.min(1, 'VALIDATION.STEAM_ID_REQUIRED')
.regex(/^\d{17}$/, 'VALIDATION.STEAM_ID_INVALID');
// Same rule as the Auth service: 3-24 letters, digits, space, _ . - and no space at the ends.
// An empty field means "no display name" and is sent as null.
const displayNameField = z
.string()
.trim()
.optional()
.transform((v) => (v ? v : null))
.refine((v) => v === null || /^[\p{L}\p{N}_.-][\p{L}\p{N} _.-]{1,22}[\p{L}\p{N}_.-]$/u.test(v), {
message: 'VALIDATION.DISPLAY_NAME_INVALID',
});
export const registrationSchema = z
.object({
email: emailField,
@@ -29,6 +40,7 @@ export const registrationSchema = z
passwordConfirmation: z.string().min(1, 'VALIDATION.PASSWORD_CONFIRMATION_REQUIRED'),
inviteCode: inviteCodeField,
steamId: steamIdField,
displayName: displayNameField,
})
.refine((d) => d.password === d.passwordConfirmation, {
path: ['passwordConfirmation'],
@@ -39,4 +51,5 @@ export const settingsSchema = z.object({
useDarkTheme: z.boolean(),
language: z.enum(['en', 'de'], {message: 'VALIDATION.LANGUAGE_INVALID'}),
steamId: steamIdField,
displayName: displayNameField,
});
+17
View File
@@ -28,3 +28,20 @@ describe('steamId validation', () => {
expect(result.steamId).toBe('76561198005679788');
});
});
describe('displayName validation', () => {
const withName = (displayName) => ({...settings('76561198005679788'), displayName});
it('turns an empty field into null (no display name)', () => {
expect(settingsSchema.parse(withName(' ')).displayName).toBeNull();
});
it('trims and keeps a valid name', () => {
expect(settingsSchema.parse(withName(' Lobo Corp_1 ')).displayName).toBe('Lobo Corp_1');
});
it.each(['ab', '<script>', 'a'.repeat(25)])('rejects %j', (displayName) => {
expect(settingsSchema.safeParse(withName(displayName)).success).toBe(false);
});
});