refactor(webapp): make login, registration, password-reset layout brandable (#8440)

* Make login, registration, password-reset layout brandable

- Rename some variables related to this

* Remove experimental code

* add lodash types

* fix build

fix type

---------

Co-authored-by: Ulf Gebhardt <ulf.gebhardt@webcraft-media.de>
This commit is contained in:
Wolfgang Huß 2025-04-28 18:58:35 +02:00 committed by GitHub
parent d7d8a242cd
commit 48c7bd0033
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 85 additions and 44 deletions

View File

@ -105,6 +105,7 @@
"@eslint-community/eslint-plugin-eslint-comments": "^4.5.0",
"@faker-js/faker": "9.7.0",
"@types/jest": "^29.5.14",
"@types/lodash": "^4.17.16",
"@types/node": "^22.15.2",
"@types/uuid": "~9.0.1",
"@typescript-eslint/eslint-plugin": "^5.62.0",

View File

@ -1,5 +1,2 @@
// this file is duplicated in `backend/src/config/metadata` and `webapp/constants/metadata.js`
export default {
NONCE_LENGTH: 5,
INVITE_CODE_LENGTH: 6,
}
// this file is duplicated in `backend/src/config/registration.ts` and `webapp/constants/registration.js`
export default {}

View File

@ -0,0 +1,12 @@
// this file is duplicated in `backend/src/config/registrationBranded.ts` and `webapp/constants/registrationBranded.js`
import { merge } from 'lodash'
import registration from '@constants/registration'
const defaultRegistration = {
NONCE_LENGTH: 5,
INVITE_CODE_LENGTH: 6,
LAYOUT: 'no-header',
}
export default merge(defaultRegistration, registration)

View File

@ -87,8 +87,9 @@ export default async function scrape(url) {
throw new ApolloError('Not found', 'NOT_FOUND')
}
return {
type: 'link',
...output,
if (!output.type) {
output.type = 'link'
}
return output
}

View File

@ -1,9 +1,9 @@
import CONSTANTS_REGISTRATION from '@constants/registration'
import registrationConstants from '@constants/registrationBranded'
export default function generateInviteCode() {
// 6 random numbers in [ 0, 35 ] are 36 possible numbers (10 [0-9] + 26 [A-Z])
return Array.from(
{ length: CONSTANTS_REGISTRATION.INVITE_CODE_LENGTH },
{ length: registrationConstants.INVITE_CODE_LENGTH },
(n: number = Math.floor(Math.random() * 36)) => {
// n > 9: it is a letter (ASCII 65 is A) -> 10 + 55 = 65
// else: it is a number (ASCII 48 is 0) -> 0 + 48 = 48

View File

@ -1,9 +1,9 @@
import CONSTANTS_REGISTRATION from '@constants/registration'
import registrationConstants from '@constants/registrationBranded'
// TODO: why this is not used in resolver 'requestPasswordReset'?
export default function generateNonce() {
return Array.from(
{ length: CONSTANTS_REGISTRATION.NONCE_LENGTH },
{ length: registrationConstants.NONCE_LENGTH },
(n: number = Math.floor(Math.random() * 10)) => {
return String.fromCharCode(n + 48)
},

View File

@ -5,7 +5,7 @@
import { createTestClient } from 'apollo-server-testing'
import gql from 'graphql-tag'
import CONSTANTS_REGISTRATION from '@constants/registration'
import registrationConstants from '@constants/registrationBranded'
import Factory, { cleanDatabase } from '@db/factories'
import { getDriver } from '@db/neo4j'
import createServer from '@src/server'
@ -116,7 +116,7 @@ describe('inviteCodes', () => {
GenerateInviteCode: {
code: expect.stringMatching(
new RegExp(
`^[0-9A-Z]{${CONSTANTS_REGISTRATION.INVITE_CODE_LENGTH},${CONSTANTS_REGISTRATION.INVITE_CODE_LENGTH}}$`,
`^[0-9A-Z]{${registrationConstants.INVITE_CODE_LENGTH},${registrationConstants.INVITE_CODE_LENGTH}}$`,
),
),
expiresAt: null,
@ -142,7 +142,7 @@ describe('inviteCodes', () => {
GenerateInviteCode: {
code: expect.stringMatching(
new RegExp(
`^[0-9A-Z]{${CONSTANTS_REGISTRATION.INVITE_CODE_LENGTH},${CONSTANTS_REGISTRATION.INVITE_CODE_LENGTH}}$`,
`^[0-9A-Z]{${registrationConstants.INVITE_CODE_LENGTH},${registrationConstants.INVITE_CODE_LENGTH}}$`,
),
),
expiresAt: nextWeek.toISOString(),

View File

@ -5,7 +5,7 @@
import { createTestClient } from 'apollo-server-testing'
import gql from 'graphql-tag'
import CONSTANTS_REGISTRATION from '@constants/registration'
import registrationConstants from '@constants/registrationBranded'
import Factory, { cleanDatabase } from '@db/factories'
import { getNeode, getDriver } from '@db/neo4j'
import createServer from '@src/server'
@ -118,7 +118,7 @@ describe('passwordReset', () => {
const resets = await getAllPasswordResets()
const [reset] = resets
const { nonce } = reset.properties
expect(nonce).toHaveLength(CONSTANTS_REGISTRATION.NONCE_LENGTH)
expect(nonce).toHaveLength(registrationConstants.NONCE_LENGTH)
})
})
})

View File

@ -7,7 +7,7 @@
import bcrypt from 'bcryptjs'
import { v4 as uuid } from 'uuid'
import CONSTANTS_REGISTRATION from '@constants/registration'
import registrationConstants from '@constants/registrationBranded'
import createPasswordReset from './helpers/createPasswordReset'
@ -15,7 +15,7 @@ export default {
Mutation: {
requestPasswordReset: async (_parent, { email }, { driver }) => {
// TODO: why this is generated differntly from 'backend/src/schema/resolvers/helpers/generateNonce.js'?
const nonce = uuid().substring(0, CONSTANTS_REGISTRATION.NONCE_LENGTH)
const nonce = uuid().substring(0, registrationConstants.NONCE_LENGTH)
return createPasswordReset({ driver, nonce, email })
},
resetPassword: async (_parent, { email, nonce, newPassword }, { driver }) => {

View File

@ -2061,6 +2061,11 @@
"@types/koa-compose" "*"
"@types/node" "*"
"@types/lodash@^4.17.16":
version "4.17.16"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.16.tgz#94ae78fab4a38d73086e962d0b65c30d816bfb0a"
integrity sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g==
"@types/long@^4.0.0":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9"

View File

@ -23,7 +23,7 @@
<script>
import gql from 'graphql-tag'
import CONSTANTS_REGISTRATION from './../../constants/registration'
import registrationConstants from '~/constants/registration'
export const isValidInviteCodeQuery = gql`
query ($code: ID!) {
@ -43,11 +43,11 @@ export default {
formSchema: {
inviteCode: {
type: 'string',
min: CONSTANTS_REGISTRATION.INVITE_CODE_LENGTH,
max: CONSTANTS_REGISTRATION.INVITE_CODE_LENGTH,
min: registrationConstants.INVITE_CODE_LENGTH,
max: registrationConstants.INVITE_CODE_LENGTH,
required: true,
message: this.$t('components.registration.invite-code.form.validations.length', {
inviteCodeLength: CONSTANTS_REGISTRATION.INVITE_CODE_LENGTH,
inviteCodeLength: registrationConstants.INVITE_CODE_LENGTH,
}),
},
},

View File

@ -29,7 +29,7 @@
<script>
import gql from 'graphql-tag'
import { isEmail } from 'validator'
import CONSTANTS_REGISTRATION from './../../constants/registration'
import registrationConstants from '~/constants/registration'
import EmailDisplayAndVerify from './EmailDisplayAndVerify'
@ -54,11 +54,11 @@ export default {
formSchema: {
nonce: {
type: 'string',
min: CONSTANTS_REGISTRATION.NONCE_LENGTH,
max: CONSTANTS_REGISTRATION.NONCE_LENGTH,
min: registrationConstants.NONCE_LENGTH,
max: registrationConstants.NONCE_LENGTH,
required: true,
message: this.$t('components.registration.email-nonce.form.validations.length', {
nonceLength: CONSTANTS_REGISTRATION.NONCE_LENGTH,
nonceLength: registrationConstants.NONCE_LENGTH,
}),
},
},

View File

@ -0,0 +1 @@
export default {}

View File

@ -0,0 +1,8 @@
import { merge } from 'lodash'
import login from '~/constants/login.js'
const defaultLogin = {
LAYOUT: 'no-header',
}
export default merge(defaultLogin, login)

View File

@ -1,5 +1,2 @@
// this file is duplicated in `backend/src/config/metadata.js` and `webapp/constants/metadata.js`
export default {
NONCE_LENGTH: 5,
INVITE_CODE_LENGTH: 6,
}
// this file is duplicated in `backend/src/config/registration.ts` and `webapp/constants/registration.js`
export default {}

View File

@ -0,0 +1,12 @@
// this file is duplicated in `backend/src/config/registrationBranded.ts` and `webapp/constants/registrationBranded.js`
import { merge } from 'lodash'
import registration from '~/constants/registration.js'
const defaultRegistration = {
NONCE_LENGTH: 5,
INVITE_CODE_LENGTH: 6,
LAYOUT: 'no-header',
}
export default merge(defaultRegistration, registration)

View File

@ -1,16 +1,19 @@
<template>
<div class="login-page">
<transition name="fade" appear>
<login-form @success="handleSuccess" />
</transition>
</div>
</template>
<script>
import LoginForm from '~/components/LoginForm/LoginForm.vue'
import loginConstants from '~/constants/loginBranded.js'
import { VERSION } from '~/constants/terms-and-conditions-version.js'
import { mapGetters } from 'vuex'
export default {
layout: 'no-header',
layout: loginConstants.LAYOUT,
components: {
LoginForm,
},

View File

@ -17,6 +17,7 @@
<script>
import links from '~/constants/links.js'
import metadata from '~/constants/metadata.js'
import loginConstants from '~/constants/loginBranded.js'
import LocaleSwitch from '~/components/LocaleSwitch/LocaleSwitch'
import Logo from '~/components/Logo/Logo'
import PageParamsLink from '~/components/_new/features/PageParamsLink/PageParamsLink.vue'
@ -27,7 +28,7 @@ export default {
Logo,
PageParamsLink,
},
layout: 'no-header',
layout: loginConstants.LAYOUT,
data() {
return {
metadata,

View File

@ -313,7 +313,7 @@ describe('Registration', () => {
it('renders', async () => {
wrapper = await Wrapper()
expect(wrapper.classes('registration-slider')).toBe(true)
expect(wrapper.find('.registration-slider')).toBeTruthy()
})
// The asyncTests must go last

View File

@ -1,16 +1,19 @@
<template>
<div class="registration-page">
<registration-slider
:registrationType="registrationType.method"
:activePage="registrationType.activePage"
:overwriteSliderData="overwriteSliderData"
/>
</div>
</template>
<script>
import registrationConstants from '~/constants/registrationBranded.js'
import RegistrationSlider from '~/components/Registration/RegistrationSlider'
export default {
layout: 'no-header',
layout: registrationConstants.LAYOUT,
name: 'Registration',
components: {
RegistrationSlider,