Merge branch 'dlt_inspector_as_submodule' into dlt_export_existing_transactions_drizzleOrm

This commit is contained in:
einhornimmond 2025-11-26 14:22:42 +01:00
commit 5fa6ef9d8c
147 changed files with 1226 additions and 1322 deletions

View File

@ -92,19 +92,4 @@ jobs:
bun install --global --no-save turbo@^2
- name: Backend | Typecheck
run: turbo backend#typecheck backend#build
locales:
if: needs.files-changed.outputs.backend == 'true'
name: Locales - Backend
needs: files-changed
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: install bun
uses: oven-sh/setup-bun@v2
- name: Backend | Locales
run: cd backend && bun locales
run: turbo backend#typecheck backend#build

View File

@ -43,3 +43,18 @@ jobs:
- name: typecheck && unit test
run: turbo core#test core#typecheck
locales:
if: needs.files-changed.outputs.core == 'true'
name: Locales - Core
needs: files-changed
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: install bun
uses: oven-sh/setup-bun@v2
- name: Core | Locales
run: cd core && bun locales

View File

@ -4,9 +4,18 @@ All notable changes to this project will be documented in this file. Dates are d
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
#### [v2.7.0](https://github.com/gradido/gradido/compare/v2.7.0...v2.7.0)
#### [v2.7.1](https://github.com/gradido/gradido/compare/v2.7.0...v2.7.1)
- fixes [`414ff8a`](https://github.com/gradido/gradido/commit/414ff8ac5a7477109f80123ccca5c4c8ed4511b2)
- feat(frontend): new startpage images [`#3576`](https://github.com/gradido/gradido/pull/3576)
- feat(frontend): update login subtitle [`#3574`](https://github.com/gradido/gradido/pull/3574)
- feat(frontend): update copy symbol and change link order [`#3575`](https://github.com/gradido/gradido/pull/3575)
- fix(backend): correct seeding [`#3572`](https://github.com/gradido/gradido/pull/3572)
- feat(dlt): dlt-connector takes care of gradido node [`#3568`](https://github.com/gradido/gradido/pull/3568)
- refactor(dlt): upgrade dlt for using gradido blockchain lib and hiero [`#3551`](https://github.com/gradido/gradido/pull/3551)
- chore(release): v2.7.0 [`#3557`](https://github.com/gradido/gradido/pull/3557)
- feat(federation): use own table for handshake state [`#3555`](https://github.com/gradido/gradido/pull/3555)
- fix(workflow): use bun instead of yarn [`#3550`](https://github.com/gradido/gradido/pull/3550)
- feat(workflow): adjust for new bun version [`#3554`](https://github.com/gradido/gradido/pull/3554)
#### [v2.7.0](https://github.com/gradido/gradido/compare/2.6.1...v2.7.0)

View File

@ -189,11 +189,11 @@ describe('test', () => {
```ts
import { clearLogs, printLogs } from 'config-schema/test/testSetup'
```
- vitest (frontend, admin, database):
- vitest (frontend, admin):
```ts
import { clearLogs, printLogs } from 'config-schema/test/testSetup.vitest'
```
- bun (shared, core):
- bun (shared, core, database):
```ts
import { clearLogs, printLogs } from 'config-schema/test/testSetup.bun'
```

View File

@ -3,7 +3,7 @@
"description": "Administration Interface for Gradido",
"main": "index.js",
"author": "Gradido Academy - https://www.gradido.net",
"version": "2.7.0",
"version": "2.7.1",
"license": "Apache-2.0",
"scripts": {
"dev": "vite",

View File

@ -22,7 +22,7 @@ KLICKTIPP_APIKEY_DE=SomeFakeKeyDE
KLICKTIPP_APIKEY_EN=SomeFakeKeyEN
# DltConnector
DLT_CONNECTOR=true
DLT_ACTIVE=false
DLT_CONNECTOR_URL=http://localhost:6010
# Community

View File

@ -1,77 +0,0 @@
# Server
PORT=4000
JWT_SECRET=secret123
JWT_EXPIRES_IN=10m
GRAPHIQL=false
GDT_API_URL=https://gdt.gradido.net
# Database
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USER=root
DB_PASSWORD=
DB_DATABASE=gradido_community
TYPEORM_LOGGING_RELATIVE_PATH=typeorm.backend.log
# Klicktipp
KLICKTIPP=false
KLICKTTIPP_API_URL=https://api.klicktipp.com
KLICKTIPP_USER=gradido_test
KLICKTIPP_PASSWORD=secret321
KLICKTIPP_APIKEY_DE=SomeFakeKeyDE
KLICKTIPP_APIKEY_EN=SomeFakeKeyEN
# DltConnector
DLT_CONNECTOR=true
DLT_CONNECTOR_URL=http://localhost:6010
# Community
COMMUNITY_NAME=Gradido Entwicklung
COMMUNITY_URL=http://localhost
COMMUNITY_REGISTER_PATH=/register
COMMUNITY_REDEEM_PATH=/redeem/{code}
COMMUNITY_REDEEM_CONTRIBUTION_PATH=/redeem/CL-{code}
COMMUNITY_DESCRIPTION=Die lokale Entwicklungsumgebung von Gradido.
COMMUNITY_SUPPORT_MAIL=support@supportmail.com
# Login Server
LOGIN_APP_SECRET=21ffbbc616fe
LOGIN_SERVER_KEY=a51ef8ac7ef1abf162fb7a65261acd7a
# EMail
EMAIL=false
EMAIL_TEST_MODUS=false
EMAIL_TEST_RECEIVER=stage1@gradido.net
EMAIL_USERNAME=gradido_email
EMAIL_SENDER=info@gradido.net
EMAIL_PASSWORD=xxx
EMAIL_SMTP_HOST=gmail.com
EMAIL_SMTP_PORT=587
EMAIL_LINK_VERIFICATION_PATH=/checkEmail/{optin}{code}
EMAIL_LINK_SETPASSWORD_PATH=/reset-password/{optin}
EMAIL_LINK_FORGOTPASSWORD_PATH=/forgot-password
EMAIL_LINK_OVERVIEW_PATH=/overview
EMAIL_CODE_VALID_TIME=1440
EMAIL_CODE_REQUEST_TIME=10
# Webhook
WEBHOOK_ELOPAGE_SECRET=secret
# SET LOG LEVEL AS NEEDED IN YOUR .ENV
# POSSIBLE VALUES: all | trace | debug | info | warn | error | fatal
LOG_LEVEL=INFO
# Federation
FEDERATION_VALIDATE_COMMUNITY_TIMER=60000
# GMS
# GMS_ACTIVE=true
# Coordinates of Illuminz test instance
#GMS_API_URL=http://54.176.169.179:3071
GMS_API_URL=http://localhost:4044
GMS_DASHBOARD_URL=http://localhost:8080
# HUMHUB
HUMHUB_ACTIVE=true
HUMHUB_API_URL=https://community-test.gradido.net
HUMHUB_JWT_KEY=GwdkIKi-rkRS0mXC4Cg3MYc3ktZh89VFmntDpNKET_dUfcIdjL_957F3nCv3brNtDfbbV81NViKaktUsfExrkH

View File

@ -24,7 +24,7 @@ KLICKTIPP_APIKEY_DE=$KLICKTIPP_APIKEY_DE
KLICKTIPP_APIKEY_EN=$KLICKTIPP_APIKEY_EN
# DltConnector
DLT_CONNECTOR=$DLT_CONNECTOR
DLT_ACTIVE=$DLT_ACTIVE
DLT_CONNECTOR_PORT=$DLT_CONNECTOR_PORT
# Community

View File

@ -114,8 +114,7 @@ COPY --chown=app:app --from=build ${DOCKER_WORKDIR}/backend/build/worker.js ./wo
# add node_modules from production_node_modules
COPY --chown=app:app --from=production-node-modules ${DOCKER_WORKDIR}/node_modules ./node_modules
# Copy locales
COPY --chown=app:app --from=build ${DOCKER_WORKDIR}/backend/locales ./locales
COPY --chown=app:app --from=build ${DOCKER_WORKDIR}/core/build/templates ./templates
# Run command
CMD ["node", "index.js"]

View File

@ -1,6 +1,6 @@
{
"name": "backend",
"version": "2.7.0",
"version": "2.7.1",
"private": false,
"description": "Gradido unified backend providing an API-Service for Gradido Transactions",
"repository": "https://github.com/gradido/gradido/backend",
@ -8,7 +8,7 @@
"author": "Gradido Academy - https://www.gradido.net",
"main": "src/index.ts",
"scripts": {
"build": "ts-node ./esbuild.config.ts && mkdirp build/templates/ && ncp src/emails/templates build/templates && mkdirp locales/ && ncp src/locales locales",
"build": "ts-node ./esbuild.config.ts && mkdirp build/templates/ && ncp ../core/build/templates build/templates",
"dev": "cross-env TZ=UTC nodemon -w src --ext ts,pug,json,css -r tsconfig-paths/register src/index.ts",
"test": "cross-env TZ=UTC NODE_ENV=development DB_DATABASE=gradido_test_backend jest --runInBand --forceExit --detectOpenHandles",
"test:coverage": "cross-env TZ=UTC NODE_ENV=development DB_DATABASE=gradido_test_backend jest --coverage --runInBand --forceExit --detectOpenHandles",
@ -19,8 +19,6 @@
"lint": "biome check --error-on-warnings .",
"lint:fix": "biome check --error-on-warnings . --write",
"lint:fix:unsafe": "biome check --fix --unsafe",
"locales": "scripts/sort.sh",
"locales:fix": "scripts/sort.sh --fix",
"start": "cross-env TZ=UTC node build/index.js",
"typecheck": "tsc --noEmit",
"clear": "rm -rf node_modules && rm -rf build && rm -rf .turbo"
@ -49,7 +47,6 @@
"@types/jest": "27.0.2",
"@types/lodash.clonedeep": "^4.5.6",
"@types/node": "^17.0.21",
"@types/nodemailer": "^6.4.4",
"@types/sodium-native": "^2.3.5",
"@types/source-map-support": "^0.5.10",
"@types/uuid": "^8.3.4",
@ -83,11 +80,9 @@
"log4js": "^6.7.1",
"mkdirp": "^3.0.1",
"ncp": "^2.0.0",
"nodemailer": "^6.6.5",
"nodemon": "^2.0.7",
"openai": "^4.87.3",
"prettier": "^3.5.3",
"pug": "^3.0.2",
"random-bigint": "^0.0.1",
"reflect-metadata": "^0.1.13",
"regenerator-runtime": "^0.14.1",

View File

@ -1,13 +0,0 @@
def walk(f):
. as $in
| if type == "object" then
reduce keys_unsorted[] as $key
( {}; . + { ($key): ($in[$key] | walk(f)) } ) | f
elif type == "array" then map( walk(f) ) | f
else f
end;
def keys_sort_by(f):
to_entries | sort_by(.key|f ) | from_entries;
walk(if type == "object" then keys_sort_by(ascii_upcase) else . end)

View File

@ -5,17 +5,17 @@ import { DltConnectorClient } from './DltConnectorClient'
describe('undefined DltConnectorClient', () => {
it('invalid url', () => {
CONFIG.DLT_CONNECTOR_URL = ''
CONFIG.DLT_CONNECTOR = true
CONFIG.DLT_ACTIVE = true
const result = DltConnectorClient.getInstance()
expect(result).toBeUndefined()
CONFIG.DLT_CONNECTOR_URL = 'http://dlt-connector:6010'
})
it('DLT_CONNECTOR is false', () => {
CONFIG.DLT_CONNECTOR = false
it('DLT_ACTIVE is false', () => {
CONFIG.DLT_ACTIVE = false
const result = DltConnectorClient.getInstance()
expect(result).toBeUndefined()
CONFIG.DLT_CONNECTOR = true
CONFIG.DLT_ACTIVE = true
})
})

View File

@ -31,7 +31,7 @@ export class DltConnectorClient {
* just one instance of each subclass around.
*/
public static getInstance(): DltConnectorClient | undefined {
if (!CONFIG.DLT_CONNECTOR || !CONFIG.DLT_CONNECTOR_URL) {
if (!CONFIG.DLT_ACTIVE || !CONFIG.DLT_CONNECTOR_URL) {
logger.info(`dlt-connector are disabled via config...`)
return
}

View File

@ -30,7 +30,7 @@ export class DltConnectorClient {
* just one instance of each subclass around.
*/
public static getInstance(): DltConnectorClient | undefined {
if (!CONFIG.DLT_CONNECTOR || !CONFIG.DLT_CONNECTOR_URL) {
if (!CONFIG.DLT_ACTIVE || !CONFIG.DLT_CONNECTOR_URL) {
logger.info(`dlt-connector are disabled via config...`)
return
}

View File

@ -66,7 +66,7 @@ async function executeDltTransaction(draft: TransactionDraft | null, typeId: Dlt
* and update dltTransactionId of transaction in db with hiero transaction id
*/
export async function registerAddressTransaction(user: DbUser, community: DbCommunity): Promise<DbDltTransaction | null> {
if (!CONFIG.DLT_CONNECTOR) {
if (!CONFIG.DLT_ACTIVE) {
return Promise.resolve(null)
}
if (!user.id) {
@ -90,7 +90,7 @@ export async function contributionTransaction(
signingUser: DbUser,
createdAt: Date,
): Promise<DbDltTransaction | null> {
if (!CONFIG.DLT_CONNECTOR) {
if (!CONFIG.DLT_ACTIVE) {
return Promise.resolve(null)
}
const homeCommunity = await getHomeCommunity()
@ -109,7 +109,7 @@ export async function transferTransaction(
memo: string,
createdAt: Date
): Promise<DbDltTransaction | null> {
if (!CONFIG.DLT_CONNECTOR) {
if (!CONFIG.DLT_ACTIVE) {
return Promise.resolve(null)
}
// load community if not already loaded, maybe they are remote communities
@ -127,7 +127,7 @@ export async function transferTransaction(
export async function deferredTransferTransaction(senderUser: DbUser, transactionLink: DbTransactionLink)
: Promise<DbDltTransaction | null> {
if (!CONFIG.DLT_CONNECTOR) {
if (!CONFIG.DLT_ACTIVE) {
return Promise.resolve(null)
}
// load community if not already loaded
@ -140,7 +140,7 @@ export async function deferredTransferTransaction(senderUser: DbUser, transactio
export async function redeemDeferredTransferTransaction(transactionLink: DbTransactionLink, amount: string, createdAt: Date, recipientUser: DbUser)
: Promise<DbDltTransaction | null> {
if (!CONFIG.DLT_CONNECTOR) {
if (!CONFIG.DLT_ACTIVE) {
return Promise.resolve(null)
}
// load user and communities if not already loaded

View File

@ -4,7 +4,7 @@ import { User as DbUser } from 'database'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
// import { createGmsUser } from '@/apis/gms/GmsClient'
// import { GmsUser } from '@/apis/gms/model/GmsUser'
import { CONFIG } from '@/config'
import { CONFIG as CORE_CONFIG } from 'core'
import { sendUserToGms } from '@/graphql/resolver/util/sendUserToGms'
import { LogError } from '@/server/LogError'
import { initLogging } from '@/server/logger'
@ -13,7 +13,7 @@ import { getLogger } from 'log4js'
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.apis.gms.ExportUsers`)
CONFIG.EMAIL = false
CORE_CONFIG.EMAIL = false
// use force to copy over all user even if gmsRegistered is set to true
const forceMode = process.argv.includes('--force')

View File

@ -92,6 +92,7 @@ export class OpenaiClient {
if (openaiThreadEntity.updatedAt < new Date(Date.now() - OPENAI_AI_THREAD_DEFAULT_TIMEOUT_DAYS * 24 * 60 * 60 * 1000)) {
logger.info(`Openai thread for user: ${user.id} is older than ${OPENAI_AI_THREAD_DEFAULT_TIMEOUT_DAYS} days, deleting...`)
// let run async, because it could need some time, but we don't need to wait, because we create a new one nevertheless
// biome-ignore lint/complexity/noVoid: start it intentionally async without waiting for result
void this.deleteThread(openaiThreadEntity.id)
return []
}

View File

@ -17,6 +17,7 @@ const logging = {
const server = {
BACKEND_PORT: process.env.BACKEND_PORT ?? 4000,
DLT_ACTIVE: process.env.DLT_ACTIVE === 'true' || false,
JWT_SECRET: process.env.JWT_SECRET ?? 'secret123',
JWT_EXPIRES_IN: process.env.JWT_EXPIRES_IN ?? '10m',
REDEEM_JWT_TOKEN_EXPIRATION: process.env.REDEEM_JWT_TOKEN_EXPIRATION ?? '10m',
@ -41,9 +42,7 @@ const COMMUNITY_URL = process.env.COMMUNITY_URL ?? `${URL_PROTOCOL}://${COMMUNIT
const DLT_CONNECTOR_PORT = process.env.DLT_CONNECTOR_PORT ?? 6010
const dltConnector = {
DLT_CONNECTOR: process.env.DLT_CONNECTOR === 'true' || false,
DLT_CONNECTOR_URL: process.env.DLT_CONNECTOR_URL ?? `${COMMUNITY_URL}:${DLT_CONNECTOR_PORT}`,
DLT_GRADIDO_NODE_SERVER_HOME_FOLDER: process.env.DLT_GRADIDO_NODE_SERVER_HOME_FOLDER ?? '~/.gradido',
}
const community = {
@ -64,22 +63,10 @@ const loginServer = {
}
const email = {
EMAIL: process.env.EMAIL === 'true',
EMAIL_TEST_MODUS: process.env.EMAIL_TEST_MODUS === 'true',
EMAIL_TEST_RECEIVER: process.env.EMAIL_TEST_RECEIVER ?? 'stage1@gradido.net',
EMAIL_USERNAME: process.env.EMAIL_USERNAME ?? '',
EMAIL_SENDER: process.env.EMAIL_SENDER ?? 'info@gradido.net',
EMAIL_PASSWORD: process.env.EMAIL_PASSWORD ?? '',
EMAIL_SMTP_HOST: process.env.EMAIL_SMTP_HOST ?? 'mailserver',
EMAIL_SMTP_PORT: Number(process.env.EMAIL_SMTP_PORT) || 1025,
EMAIL_TLS: process.env.EMAIL_TLS !== 'false',
EMAIL_LINK_VERIFICATION:
COMMUNITY_URL + (process.env.EMAIL_LINK_VERIFICATION_PATH ?? '/checkEmail/'),
EMAIL_LINK_SETPASSWORD:
COMMUNITY_URL + (process.env.EMAIL_LINK_SETPASSWORD_PATH ?? '/reset-password/'),
EMAIL_LINK_FORGOTPASSWORD:
COMMUNITY_URL + (process.env.EMAIL_LINK_FORGOTPASSWORD_PATH ?? '/forgot-password'),
EMAIL_LINK_OVERVIEW: COMMUNITY_URL + (process.env.EMAIL_LINK_OVERVIEW_PATH ?? '/overview'),
// time in minutes a optin code is valid
EMAIL_CODE_VALID_TIME: process.env.EMAIL_CODE_VALID_TIME

View File

@ -4,6 +4,7 @@ import {
COMMUNITY_SUPPORT_MAIL,
COMMUNITY_URL,
DECAY_START_TIME,
DLT_ACTIVE,
GDT_ACTIVE,
GDT_API_URL,
GMS_ACTIVE,
@ -27,6 +28,7 @@ export const schema = Joi.object({
COMMUNITY_DESCRIPTION,
COMMUNITY_SUPPORT_MAIL,
DECAY_START_TIME,
DLT_ACTIVE,
GDT_API_URL,
GDT_ACTIVE,
GMS_ACTIVE,
@ -68,90 +70,11 @@ export const schema = Joi.object({
.default('http://0.0.0.0/redeem/CL-')
.required(),
DLT_CONNECTOR: Joi.boolean()
.description('Flag to indicate if DLT-Connector is used. (Still in development)')
.default(false)
.required(),
DLT_CONNECTOR_URL: Joi.string()
.uri({ scheme: ['http', 'https'] })
.default('http://localhost:6010')
.when('DLT_CONNECTOR', { is: true, then: Joi.required() })
.description('The URL for GDT API endpoint'),
DLT_GRADIDO_NODE_SERVER_HOME_FOLDER: Joi.string()
.default('~/.gradido')
.description('The home folder for the gradido dlt node server'),
EMAIL: Joi.boolean()
.default(false)
.description('Enable or disable email functionality')
.required(),
EMAIL_TEST_MODUS: Joi.boolean()
.default(false)
.description('When enabled, all emails are sended to EMAIL_TEST_RECEIVER')
.optional(),
EMAIL_TEST_RECEIVER: Joi.string()
.email()
.default('stage1@gradido.net')
.when('EMAIL_TEST_MODUS', { is: true, then: Joi.required() })
.description('Email address used in test mode'),
EMAIL_USERNAME: Joi.alternatives().conditional(Joi.ref('EMAIL'), {
is: true,
then: Joi.alternatives().conditional(Joi.ref('NODE_ENV'), {
is: 'development',
then: Joi.string()
.allow('')
.description('Username for SMTP authentication (optional in development)'),
otherwise: Joi.string()
.pattern(/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/)
.description('Valid SMTP username required in production')
.required(),
}),
otherwise: Joi.string().allow('').optional(),
}),
EMAIL_SENDER: Joi.string()
.email()
.when('EMAIL', { is: true, then: Joi.required() })
.default('info@gradido.net')
.description('Email address used as sender'),
EMAIL_PASSWORD: Joi.alternatives().conditional(Joi.ref('EMAIL'), {
is: true,
then: Joi.alternatives().conditional(Joi.ref('NODE_ENV'), {
is: 'development',
then: Joi.string()
.allow('')
.description('Password for SMTP authentication (optional in development)'),
otherwise: Joi.string()
.min(8)
.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&#]).{8,}$/)
.description(
'Password must be at least 8 characters long, include uppercase and lowercase letters, a number, and a special character',
)
.required(),
}),
otherwise: Joi.string().allow('').optional(),
}),
EMAIL_SMTP_HOST: Joi.string()
.hostname()
.when('EMAIL', { is: true, then: Joi.required() })
.default('mailserver')
.description('SMTP server hostname'),
EMAIL_SMTP_PORT: Joi.number()
.integer()
.positive()
.when('EMAIL', { is: true, then: Joi.required() })
.default(1025)
.description('SMTP server port'),
EMAIL_TLS: Joi.boolean().default(true).description('Enable or disable TLS for SMTP').optional(),
.when('DLT_ACTIVE', { is: true, then: Joi.required() })
.description('The URL for DLT connector'),
EMAIL_LINK_VERIFICATION: Joi.string()
.uri({ scheme: ['http', 'https'] })
@ -175,17 +98,6 @@ export const schema = Joi.object({
.description('Email Verification link for set initial Password.')
.required(),
EMAIL_LINK_FORGOTPASSWORD: Joi.string()
.uri({ scheme: ['http', 'https'] })
.custom((value: string, helpers: Joi.CustomHelpers<string>): string | Joi.ErrorReport => {
if (!value.startsWith(helpers.state.ancestors[0].COMMUNITY_URL)) {
return helpers.error('string.pattern.base', { value, communityUrl: COMMUNITY_URL })
}
return value
})
.description('Email Verification link for set new Password, when old Password was forgotten.')
.required(),
EMAIL_LINK_OVERVIEW: Joi.string()
.uri({ scheme: ['http', 'https'] })
.custom((value: string, helpers: Joi.CustomHelpers<string>): string | Joi.ErrorReport => {
@ -213,7 +125,7 @@ export const schema = Joi.object({
.description('Time in minutes before a new code can be requested')
.required(),
FEDERATION_VALIDATE_COMMUNITY_TIMER: Joi.number()
FEDERATION_VALIDATE_COMMUNITY_TIMER: Joi.number()
.integer()
.min(1000)
.default(60000)

View File

@ -1,225 +0,0 @@
import { Decimal } from 'decimal.js-light'
import { CONFIG } from '@/config'
import { decimalSeparatorByLanguage } from 'core'
import { sendEmailTranslated } from './sendEmailTranslated'
export interface ContributionEmailCommonData {
firstName: string
lastName: string
email: string
language: string
senderFirstName: string
senderLastName: string
contributionMemo: string
contributionFrontendLink: string
}
function toContributionEmailLocales(data: ContributionEmailCommonData): Record<string, unknown> {
return {
firstName: data.firstName,
lastName: data.lastName,
locale: data.language,
senderFirstName: data.senderFirstName,
senderLastName: data.senderLastName,
contributionMemo: data.contributionMemo,
contributionFrontendLink: data.contributionFrontendLink,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
}
}
export const sendAddedContributionMessageEmail = (
data: ContributionEmailCommonData & {
message: string
},
): Promise<Record<string, unknown> | boolean | null> => {
return sendEmailTranslated({
receiver: {
to: `${data.firstName} ${data.lastName} <${data.email}>`,
},
template: 'addedContributionMessage',
locals: {
...toContributionEmailLocales(data),
message: data.message,
},
})
}
export const sendAccountActivationEmail = (data: {
firstName: string
lastName: string
email: string
language: string
activationLink: string
timeDurationObject: Record<string, unknown>
logoUrl?: string | null
}): Promise<Record<string, unknown> | boolean | null> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'accountActivation',
locals: {
firstName: data.firstName,
lastName: data.lastName,
locale: data.language,
activationLink: data.activationLink,
timeDurationObject: data.timeDurationObject,
logoUrl: data.logoUrl,
resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL,
},
})
}
export const sendAccountMultiRegistrationEmail = (data: {
firstName: string
lastName: string
email: string
language: string
}): Promise<Record<string, unknown> | boolean | null> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'accountMultiRegistration',
locals: {
firstName: data.firstName,
lastName: data.lastName,
locale: data.language,
resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL,
},
})
}
export const sendContributionConfirmedEmail = (
data: ContributionEmailCommonData & {
contributionAmount: Decimal
},
): Promise<Record<string, unknown> | boolean | null> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'contributionConfirmed',
locals: {
...toContributionEmailLocales(data),
contributionAmount: decimalSeparatorByLanguage(data.contributionAmount, data.language),
},
})
}
export const sendContributionChangedByModeratorEmail = (
data: ContributionEmailCommonData & {
contributionMemoUpdated: string
},
): Promise<Record<string, unknown> | boolean | null> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'contributionChangedByModerator',
locals: {
...toContributionEmailLocales(data),
contributionMemoUpdated: data.contributionMemoUpdated,
},
})
}
export const sendContributionDeletedEmail = (
data: ContributionEmailCommonData,
): Promise<Record<string, unknown> | boolean | null> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'contributionDeleted',
locals: toContributionEmailLocales(data),
})
}
export const sendContributionDeniedEmail = (
data: ContributionEmailCommonData,
): Promise<Record<string, unknown> | boolean | null> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'contributionDenied',
locals: toContributionEmailLocales(data),
})
}
export const sendResetPasswordEmail = (data: {
firstName: string
lastName: string
email: string
language: string
resetLink: string
timeDurationObject: Record<string, unknown>
}): Promise<Record<string, unknown> | boolean | null> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'resetPassword',
locals: {
firstName: data.firstName,
lastName: data.lastName,
locale: data.language,
resetLink: data.resetLink,
timeDurationObject: data.timeDurationObject,
resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL,
},
})
}
export const sendTransactionLinkRedeemedEmail = (data: {
firstName: string
lastName: string
email: string
language: string
senderFirstName: string
senderLastName: string
senderEmail: string
transactionMemo: string
transactionAmount: Decimal
}): Promise<Record<string, unknown> | boolean | null> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'transactionLinkRedeemed',
locals: {
firstName: data.firstName,
lastName: data.lastName,
locale: data.language,
senderFirstName: data.senderFirstName,
senderLastName: data.senderLastName,
senderEmail: data.senderEmail,
transactionMemo: data.transactionMemo,
transactionAmount: decimalSeparatorByLanguage(data.transactionAmount, data.language),
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL,
},
})
}
export const sendTransactionReceivedEmail = (data: {
firstName: string
lastName: string
email: string
language: string
senderFirstName: string
senderLastName: string
senderEmail: string
memo: string
transactionAmount: Decimal
}): Promise<Record<string, unknown> | boolean | null> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'transactionReceived',
locals: {
firstName: data.firstName,
lastName: data.lastName,
locale: data.language,
memo: data.memo,
senderFirstName: data.senderFirstName,
senderLastName: data.senderLastName,
senderEmail: data.senderEmail,
transactionAmount: decimalSeparatorByLanguage(data.transactionAmount, data.language),
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL,
},
})
}

View File

@ -5,7 +5,6 @@ import { DataSource } from 'typeorm'
import { v4 as uuidv4 } from 'uuid'
import { cleanDB, testEnvironment } from '@test/helpers'
import { i18n as localization } from '@test/testSetup'
import { userFactory } from '@/seeds/factory/user'
import { login, updateHomeCommunityQuery } from '@/seeds/graphql/mutations'
@ -43,7 +42,7 @@ const peterLoginData = {
}
beforeAll(async () => {
testEnv = await testEnvironment(getLogger('apollo'), localization)
testEnv = await testEnvironment(getLogger('apollo'))
mutate = testEnv.mutate
query = testEnv.query
con = testEnv.con

View File

@ -5,10 +5,9 @@ import { DataSource } from 'typeorm'
import { ContributionStatus } from '@enum/ContributionStatus'
import { cleanDB, resetToken, testEnvironment } from '@test/helpers'
import { i18n as localization } from '@test/testSetup'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { sendAddedContributionMessageEmail } from '@/emails/sendEmailVariants'
import { sendAddedContributionMessageEmail } from 'core'
import { EventType } from '@/event/Events'
import { userFactory } from '@/seeds/factory/user'
import {
@ -30,14 +29,12 @@ const interactionLogger = getLogger(
)
jest.mock('@/password/EncryptorUtils')
jest.mock('@/emails/sendEmailVariants', () => {
const originalModule = jest.requireActual('@/emails/sendEmailVariants')
jest.mock('core', () => {
const originalModule = jest.requireActual('core')
return {
__esModule: true,
...originalModule,
sendAddedContributionMessageEmail: jest.fn((a) =>
originalModule.sendAddedContributionMessageEmail(a),
),
sendAddedContributionMessageEmail: jest.fn(),
}
})
@ -51,7 +48,7 @@ let testEnv: {
let result: any
beforeAll(async () => {
testEnv = await testEnvironment(logger, localization)
testEnv = await testEnvironment(logger)
mutate = testEnv.mutate
con = testEnv.con
await cleanDB()

View File

@ -14,7 +14,7 @@ import { Order } from '@enum/Order'
import { ContributionMessage, ContributionMessageListResult } from '@model/ContributionMessage'
import { RIGHTS } from '@/auth/RIGHTS'
import { sendAddedContributionMessageEmail } from '@/emails/sendEmailVariants'
import { sendAddedContributionMessageEmail } from 'core'
import {
EVENT_ADMIN_CONTRIBUTION_MESSAGE_CREATE,
EVENT_CONTRIBUTION_MESSAGE_CREATE,

View File

@ -14,14 +14,13 @@ import {
resetToken,
testEnvironment,
} from '@test/helpers'
import { i18n as localization } from '@test/testSetup'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import {
sendContributionConfirmedEmail,
sendContributionDeletedEmail,
sendContributionDeniedEmail,
} from '@/emails/sendEmailVariants'
} from 'core'
import { EventType } from '@/event/Events'
import { creations } from '@/seeds/creation/index'
import { creationFactory } from '@/seeds/factory/creation'
@ -54,7 +53,17 @@ import { getFirstDayOfPreviousNMonth } from 'core'
import { getLogger } from 'config-schema/test/testSetup'
import { getLogger as originalGetLogger } from 'log4js'
jest.mock('@/emails/sendEmailVariants')
jest.mock('core', () => {
const originalModule = jest.requireActual('core')
return {
__esModule: true,
...originalModule,
sendContributionDeniedEmail: jest.fn(),
sendContributionConfirmedEmail: jest.fn(),
sendContributionDeletedEmail: jest.fn(),
sendEmailTranslated: jest.fn(),
}
})
jest.mock('@/password/EncryptorUtils')
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.server.LogError`)
@ -77,7 +86,7 @@ let contributionToDelete: any
let bibiCreatedContribution: Contribution
beforeAll(async () => {
testEnv = await testEnvironment(originalGetLogger('apollo'), localization)
testEnv = await testEnvironment(originalGetLogger('apollo'))
mutate = testEnv.mutate
query = testEnv.query
con = testEnv.con

View File

@ -21,15 +21,15 @@ import { AdminUpdateContribution } from '@model/AdminUpdateContribution'
import { Contribution, ContributionListResult } from '@model/Contribution'
import { OpenCreation } from '@model/OpenCreation'
import { UnconfirmedContribution } from '@model/UnconfirmedContribution'
import { TransactionTypeId } from 'core'
import { RIGHTS } from '@/auth/RIGHTS'
import {
fullName,
sendContributionChangedByModeratorEmail,
sendContributionConfirmedEmail,
sendContributionDeletedEmail,
sendContributionDeniedEmail,
} from '@/emails/sendEmailVariants'
TransactionTypeId
} from 'core'
import {
EVENT_ADMIN_CONTRIBUTION_CONFIRM,
EVENT_ADMIN_CONTRIBUTION_CREATE,
@ -44,7 +44,6 @@ import { UpdateUnconfirmedContributionContext } from '@/interactions/updateUncon
import { LogError } from '@/server/LogError'
import { Context, getClientTimezoneOffset, getUser } from '@/server/context'
import { TRANSACTIONS_LOCK } from 'database'
import { fullName } from 'core'
import { calculateDecay, Decay } from 'shared'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'

View File

@ -5,6 +5,7 @@ import { DataSource } from 'typeorm'
import { cleanDB, testEnvironment } from '@test/helpers'
import { CONFIG as CORE_CONFIG } from 'core'
import { CONFIG } from '@/config'
import { writeHomeCommunityEntry } from '@/seeds/community'
import { createUser, forgotPassword, setPassword } from '@/seeds/graphql/mutations'
@ -21,7 +22,7 @@ let testEnv: {
CONFIG.EMAIL_CODE_VALID_TIME = 1440
CONFIG.EMAIL_CODE_REQUEST_TIME = 10
CONFIG.EMAIL = false
CORE_CONFIG.EMAIL = false
beforeAll(async () => {
testEnv = await testEnvironment()

View File

@ -2,7 +2,6 @@ import { Event as DbEvent, UserContact } from 'database'
import { GraphQLError } from 'graphql'
import { cleanDB, resetToken, testEnvironment } from '@test/helpers'
import { i18n as localization } from '@test/testSetup'
import { getLogger } from 'config-schema/test/testSetup'
import { EventType } from '@/event/Events'
@ -20,7 +19,7 @@ let mutate: any
let con: any
beforeAll(async () => {
testEnv = await testEnvironment(logger, localization)
testEnv = await testEnvironment(logger)
mutate = testEnv.mutate
con = testEnv.con
await cleanDB()

View File

@ -43,7 +43,7 @@ const logErrorLogger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.server.LogError`)
jest.mock('@/password/EncryptorUtils')
CONFIG.DLT_CONNECTOR = false
CONFIG.DLT_ACTIVE = false
// mock semaphore to allow use fake timers
jest.mock('database/src/util/TRANSACTIONS_LOCK')

View File

@ -34,12 +34,13 @@ import { peterLustig } from '@/seeds/users/peter-lustig'
import { stephenHawking } from '@/seeds/users/stephen-hawking'
import { getLogger } from 'config-schema/test/testSetup'
import { CONFIG } from '@/config'
import { CONFIG as CORE_CONFIG} from 'core'
jest.mock('@/password/EncryptorUtils')
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.server.LogError`)
CONFIG.DLT_CONNECTOR = false
CONFIG.EMAIL = false
CONFIG.DLT_ACTIVE = false
CORE_CONFIG.EMAIL = false
let mutate: ApolloServerTestClient['mutate']
let query: ApolloServerTestClient['query']
@ -72,7 +73,6 @@ let peter: User
let homeCom: DbCommunity
let foreignCom: DbCommunity
let fedForeignCom: DbFederatedCommunity
describe('send coins', () => {
beforeAll(async () => {

View File

@ -1,14 +1,11 @@
import {
AppDatabase,
countOpenPendingTransactions,
Community as DbCommunity,
DltTransaction as DbDltTransaction,
Transaction as dbTransaction,
TransactionLink as dbTransactionLink,
User as dbUser,
findUserByIdentifier,
TransactionLoggingView,
UserLoggingView
} from 'database'
import { Decimal } from 'decimal.js-light'
import { Args, Authorized, Ctx, Mutation, Query, Resolver } from 'type-graphql'
@ -20,21 +17,22 @@ import { Order } from '@enum/Order'
import { Transaction } from '@model/Transaction'
import { TransactionList } from '@model/TransactionList'
import { User } from '@model/User'
import { processXComCompleteTransaction, TransactionTypeId } from 'core'
import {
fullName,
processXComCompleteTransaction,
sendTransactionLinkRedeemedEmail,
sendTransactionReceivedEmail,
TransactionTypeId
} from 'core'
import { RIGHTS } from '@/auth/RIGHTS'
import { CONFIG } from '@/config'
import {
sendTransactionLinkRedeemedEmail,
sendTransactionReceivedEmail,
} from '@/emails/sendEmailVariants'
import { EVENT_TRANSACTION_RECEIVE, EVENT_TRANSACTION_SEND } from '@/event/Events'
EVENT_TRANSACTION_RECEIVE, EVENT_TRANSACTION_SEND } from '@/event/Events'
import { LogError } from '@/server/LogError'
import { Context, getUser } from '@/server/context'
import { communityUser } from '@/util/communityUser'
import { calculateBalance } from '@/util/validate'
import { virtualDecayTransaction, virtualLinkTransaction } from '@/util/virtualTransactions'
import { fullName } from 'core'
import { TRANSACTIONS_LOCK } from 'database'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'

View File

@ -20,7 +20,6 @@ import { UserContactType } from '@enum/UserContactType'
import { ContributionLink } from '@model/ContributionLink'
import { Location } from '@model/Location'
import { cleanDB, headerPushMock, resetToken, testEnvironment } from '@test/helpers'
import { i18n as localization } from '@test/testSetup'
import { subscribe } from '@/apis/KlicktippController'
import { CONFIG } from '@/config'
@ -28,7 +27,7 @@ import {
sendAccountActivationEmail,
sendAccountMultiRegistrationEmail,
sendResetPasswordEmail,
} from '@/emails/sendEmailVariants'
} from 'core'
import { EventType } from '@/event/Events'
import { PublishNameType } from '@/graphql/enum/PublishNameType'
import { SecretKeyCryptographyCreateKey } from '@/password/EncryptorUtils'
@ -74,16 +73,15 @@ import { Location2Point } from './util/Location2Point'
jest.mock('@/apis/humhub/HumHubClient')
jest.mock('@/password/EncryptorUtils')
jest.mock('@/emails/sendEmailVariants', () => {
const originalModule = jest.requireActual('@/emails/sendEmailVariants')
jest.mock('core', () => {
const originalModule = jest.requireActual('core')
return {
__esModule: true,
...originalModule,
sendAccountActivationEmail: jest.fn((a) => originalModule.sendAccountActivationEmail(a)),
sendAccountMultiRegistrationEmail: jest.fn((a) =>
originalModule.sendAccountMultiRegistrationEmail(a),
),
sendResetPasswordEmail: jest.fn((a) => originalModule.sendResetPasswordEmail(a)),
sendAccountActivationEmail: jest.fn(),
sendAccountMultiRegistrationEmail: jest.fn(),
sendResetPasswordEmail: jest.fn(),
sendEmailTranslated: jest.fn(),
}
})
@ -112,12 +110,12 @@ let testEnv: {
}
beforeAll(async () => {
testEnv = await testEnvironment(getLogger('apollo'), localization)
testEnv = await testEnvironment(getLogger('apollo'))
mutate = testEnv.mutate
query = testEnv.query
con = testEnv.con
CONFIG.HUMHUB_ACTIVE = false
CONFIG.DLT_CONNECTOR = false
CONFIG.DLT_ACTIVE = false
await cleanDB()
})
@ -155,6 +153,7 @@ describe('UserResolver', () => {
expect(result).toEqual(
expect.objectContaining({ data: { createUser: { id: expect.any(Number) } } }),
)
})
describe('valid input data', () => {

View File

@ -10,7 +10,6 @@ import {
findUserByIdentifier
} from 'database'
import { GraphQLResolveInfo } from 'graphql'
import i18n from 'i18n'
import {
Arg,
Args,
@ -57,11 +56,6 @@ import { encode } from '@/auth/JWT'
import { RIGHTS } from '@/auth/RIGHTS'
import { CONFIG } from '@/config'
import { PublishNameLogic } from '@/data/PublishName.logic'
import {
sendAccountActivationEmail,
sendAccountMultiRegistrationEmail,
sendResetPasswordEmail,
} from '@/emails/sendEmailVariants'
import {
EVENT_ADMIN_USER_DELETE,
EVENT_ADMIN_USER_ROLE_SET,
@ -85,8 +79,12 @@ import { Context, getClientTimezoneOffset, getUser } from '@/server/context'
import { communityDbUser } from '@/util/communityUser'
import { hasElopageBuys } from '@/util/hasElopageBuys'
import { durationInMinutesFromDates, getTimeDurationObject, printTimeDuration } from '@/util/time'
import { delay } from 'core'
import {
delay,
sendAccountActivationEmail,
sendAccountMultiRegistrationEmail,
sendResetPasswordEmail,
} from 'core'
import random from 'random-bigint'
import { randombytes_random } from 'sodium-native'
@ -233,7 +231,6 @@ export class UserResolver {
logger.debug('validation of login credentials successful...')
const user = new User(dbUser)
i18n.setLocale(user.language)
// Elopage Status & Stored PublisherId
user.hasElopage = await this.hasElopage({ ...context, user: dbUser })
@ -322,7 +319,6 @@ export class UserResolver {
if (!language || !isLanguage(language)) {
language = DEFAULT_LANGUAGE
}
i18n.setLocale(language)
// check if user with email still exists?
email = email.trim().toLowerCase()
@ -764,7 +760,6 @@ export class UserResolver {
throw new LogError('Given language is not a valid language or not supported')
}
user.language = language
i18n.setLocale(language)
updated = true
}

View File

@ -22,12 +22,13 @@ import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
import { bobBaumeister } from '@/seeds/users/bob-baumeister'
import { peterLustig } from '@/seeds/users/peter-lustig'
import { CONFIG } from '@/config'
import { CONFIG as CORE_CONFIG } from 'core'
import { TRANSACTIONS_LOCK } from 'database'
jest.mock('@/password/EncryptorUtils')
CONFIG.DLT_CONNECTOR = false
CONFIG.EMAIL = false
CONFIG.DLT_ACTIVE = false
CORE_CONFIG.EMAIL = false
let mutate: ApolloServerTestClient['mutate']
let con: DataSource

View File

@ -18,7 +18,6 @@ export const userFactory = async (
// console.log('call createUser with', JSON.stringify(user, null, 2))
const response = await mutate({ mutation: createUser, variables: user })
if (!response?.data?.createUser) {
// biome-ignore lint/suspicious/noConsole: will be used in tests where logging is mocked
// console.log(JSON.stringify(response, null, 2))
throw new Error('createUser mutation returned unexpected response')
}

View File

@ -3,6 +3,7 @@ import { entities } from 'database'
import { datatype, internet, name } from 'faker'
import { CONFIG } from '@/config'
import { CONFIG as CORE_CONFIG } from 'core'
import { createServer } from '@/server/createServer'
import { initLogging } from '@/server/logger'
@ -17,7 +18,7 @@ import { userFactory } from './factory/user'
import { transactionLinks } from './transactionLink/index'
import { users } from './users/index'
CONFIG.EMAIL = false
CORE_CONFIG.EMAIL = false
const logger = getLogger('seed')
const context = {

View File

@ -1,4 +1,5 @@
import { CONFIG } from '@/config'
import { CONFIG as CORE_CONFIG } from 'core'
import { schema } from '@/graphql/schema'
import { elopageWebhook } from '@/webhook/elopage'
import { gmsWebhook } from '@/webhook/gms'
@ -28,7 +29,6 @@ interface ServerDef {
export const createServer = async (
apolloLogger: Logger,
context: any = serverContext,
localization: i18n.I18n = i18n,
): Promise<ServerDef> => {
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.server.createServer`)
logger.debug('createServer...')
@ -73,9 +73,9 @@ export const createServer = async (
app.use(json())
// bodyparser urlencoded for elopage
app.use(urlencoded({ extended: true }))
// i18n
app.use(localization.init)
app.use(i18n.init)
// Elopage Webhook
@ -100,7 +100,7 @@ export const createServer = async (
})
apollo.applyMiddleware({ app, path: '/' })
logger.info(
`running with PRODUCTION=${CONFIG.PRODUCTION}, sending EMAIL enabled=${CONFIG.EMAIL} and EMAIL_TEST_MODUS=${CONFIG.EMAIL_TEST_MODUS} ...`,
`running with PRODUCTION=${CONFIG.PRODUCTION}, sending EMAIL enabled=${CORE_CONFIG.EMAIL} and EMAIL_TEST_MODUS=${CORE_CONFIG.EMAIL_TEST_MODUS} ...`,
)
logger.debug('createServer...successful')

View File

@ -1,4 +1,3 @@
import path from 'node:path'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import i18n from 'i18n'
import { getLogger } from 'log4js'
@ -9,7 +8,10 @@ i18n.configure({
locales: ['en', 'de'],
defaultLocale: 'en',
retryInDefaultLocale: false,
directory: path.join(__dirname, '..', 'locales'),
staticCatalog: {
en: { general: { decimalSeparator: "." } },
de: { general: { decimalSeparator: "," } },
},
// autoReload: true, // if this is activated the seeding hangs at the very end
updateFiles: false,
objectNotation: true,

View File

@ -3,8 +3,6 @@ import { entities } from 'database'
import { createServer } from '@/server/createServer'
import { i18n } from './testSetup'
import { getLogger } from 'log4js'
export const headerPushMock = jest.fn((t) => {
@ -29,8 +27,8 @@ export const cleanDB = async () => {
}
}
export const testEnvironment = async (testLogger = getLogger('apollo'), testI18n = i18n) => {
const server = await createServer( testLogger, context, testI18n)
export const testEnvironment = async (testLogger = getLogger('apollo')) => {
const server = await createServer( testLogger, context)
const con = server.con
const testClient = createTestClient(server.apollo)
const mutate = testClient.mutate

View File

@ -1,27 +1,13 @@
import 'openai/shims/node'
import { CONFIG } from '@/config'
import { i18n } from '@/server/localization'
import { CONFIG as CORE_CONFIG } from 'core'
import { getLogger, printLogs, clearLogs } from 'config-schema/test/testSetup'
CONFIG.EMAIL = true
CONFIG.EMAIL_TEST_MODUS = false
CORE_CONFIG.EMAIL = false
CORE_CONFIG.EMAIL_TEST_MODUS = false
CONFIG.HUMHUB_ACTIVE = false
CONFIG.GMS_ACTIVE = false
jest.setTimeout(1000000)
jest.mock('@/server/localization', () => {
const originalModule = jest.requireActual<typeof i18n>('@/server/localization')
return {
__esModule: true,
...originalModule,
i18n: {
init: jest.fn(),
// configure: jest.fn(),
// __: jest.fn(),
// setLocale: jest.fn(),
},
}
})
export { i18n, getLogger, printLogs, clearLogs as cleanLogs }
export { getLogger, printLogs, clearLogs as cleanLogs }

View File

@ -63,7 +63,7 @@
"typeRoots": [ /* List of folders to include type definitions from. */
"@types",
"node_modules/@types",
"../node_modules/@types"
"../node_modules/@types",
],
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
@ -86,6 +86,9 @@
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */
},
"include": [
"../core/src/types"
],
"ts-node": {
"swc": true
}

View File

@ -8,10 +8,8 @@
"locales": {},
"locales:fix": {},
"lint": {
"dependsOn": ["locales"]
},
"lint:fix": {
"dependsOn": ["locales:fix"]
},
"test": {
"dependsOn": ["database#up:backend_test", "^build"]

416
bun.lock

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "config-schema",
"version": "2.7.0",
"version": "2.7.1",
"description": "Gradido Config for validate config",
"main": "./build/index.js",
"types": "./src/index.ts",

View File

@ -35,6 +35,11 @@ export const COMMUNITY_URL = Joi.string()
.default('http://0.0.0.0')
.required()
export const DLT_ACTIVE = Joi.boolean()
.description('Flag to indicate if the DLT (Decentralized Ledger Technology) service is used.')
.default(false)
.required()
export const GRAPHQL_URI = Joi.string()
.uri({ scheme: ['http', 'https'] })
.description(

View File

@ -17,17 +17,23 @@ export function validate(schema: ObjectSchema, data: any) {
throw new Error('missing key in config validation with joi: ' + details)
}
const value = err.context.value
const description = schemaJson.keys[key]
? schema.describe().keys[key].flags.description
: 'No description available'
if (data[key] === undefined) {
throw new Error(
`Environment Variable '${key}' is missing. ${description}, details: ${details}`,
)
} else {
throw new Error(
`Error on Environment Variable ${key} with value = ${value}: ${err.message}. ${description}`,
)
try {
const description = schemaJson.keys[key]
? schema.describe().keys[key].flags.description
: 'No description available'
if (data[key] === undefined) {
throw new Error(
`Environment Variable '${key}' is missing. ${description}, details: ${details}`,
)
} else {
throw new Error(
`Error on Environment Variable ${key} with value = ${value}: ${err.message}. ${description}`,
)
}
} catch (e) {
// biome-ignore lint/suspicious/noConsole: schema validation may be run before logger is initialized
console.error('Error getting description for key ' + key + ': ' + e)
throw e
}
})
}

View File

@ -91,7 +91,8 @@ const getLoggerMocked = mock().mockImplementation((param: any) => {
})
mock.module('log4js', () => ({
getLogger: getLoggerMocked
getLogger: getLoggerMocked,
addLayout: jest.fn()
}))
export function getLogger(name: string) {

16
core/esbuild.config.ts Normal file
View File

@ -0,0 +1,16 @@
import { build } from 'esbuild'
build({
entryPoints: ['src/index.ts'],
outdir: 'build',
platform: 'node',
target: 'node18.20.7',
loader: {
'.png': 'binary',
'.jpeg': 'binary',
'.jpg': 'binary',
},
bundle: true,
sourcemap: true,
packages: 'external',
})

View File

@ -1,6 +1,6 @@
{
"name": "core",
"version": "2.7.0",
"version": "2.7.1",
"description": "Gradido Core Code, High-Level Shared Code, with dependencies on other modules",
"main": "./build/index.js",
"types": "./src/index.ts",
@ -15,37 +15,46 @@
"license": "Apache-2.0",
"private": true,
"scripts": {
"build": "esbuild src/index.ts --outdir=build --platform=node --target=node18.20.7 --bundle --packages=external",
"build": "bun esbuild.config.ts && mkdirp build/templates/ && ncp src/emails/templates build/templates",
"build:bun": "bun build src/index.ts --outdir=build --target=bun --packages=external",
"test": "bun test",
"test:debug": "bun test --inspect-brk",
"typecheck": "tsc --noEmit",
"lint": "biome check --error-on-warnings .",
"lint:fix": "biome check --error-on-warnings . --write",
"locales": "scripts/sort.sh src/emails/locales",
"locales:fix": "scripts/sort.sh src/emails/locales --fix",
"clear": "rm -rf node_modules && rm -rf build && rm -rf .turbo"
},
"dependencies": {
"database": "*",
"email-templates": "^10.0.1",
"esbuild": "^0.25.2",
"i18n": "^0.15.1",
"joi": "^17.13.3",
"jose": "^4.14.4",
"log4js": "^6.9.1",
"nodemailer": "^6.6.5",
"pug": "^3.0.2",
"shared": "*",
"sodium-native": "^3.4.1",
"zod": "^3.25.61"
},
"devDependencies": {
"@biomejs/biome": "2.0.0",
"@types/email-templates": "^10.0.4",
"@types/i18n": "^0.13.4",
"@types/minimatch": "6.0.0",
"@types/node": "^17.0.21",
"@types/nodemailer": "^6.4.4",
"@types/sodium-native": "^2.3.5",
"config-schema": "*",
"decimal.js-light": "^2.5.1",
"dotenv": "^10.0.0",
"graphql-request": "5.0.0",
"jest": "27.2.4",
"mkdirp": "^3.0.1",
"ncp": "^2.0.0",
"type-graphql": "^1.1.1",
"typescript": "^4.9.5"
},

View File

@ -4,11 +4,11 @@ ROOT_DIR=$(dirname "$0")/..
exit_code=0
for locale_file in $ROOT_DIR/src/locales/*.json
for locale_file in $ROOT_DIR/"$1"/*.json
do
jq -M 'to_entries | sort_by(.key) | from_entries' "$locale_file" > tmp.json
if [ "$*" == "--fix" ]
if [ "$2" == "--fix" ]
then
mv tmp.json "$locale_file"
else

View File

@ -20,8 +20,34 @@ const federation = {
),
}
const COMMUNITY_HOST = process.env.COMMUNITY_HOST ?? 'localhost'
const URL_PROTOCOL = process.env.URL_PROTOCOL ?? 'http'
const COMMUNITY_URL = process.env.COMMUNITY_URL ?? `${URL_PROTOCOL}://${COMMUNITY_HOST}`
const community = {
COMMUNITY_SUPPORT_MAIL: process.env.COMMUNITY_SUPPORT_MAIL ?? 'support@supportmail.com',
COMMUNITY_URL,
}
const email = {
EMAIL: process.env.EMAIL === 'true',
EMAIL_LINK_FORGOTPASSWORD:
COMMUNITY_URL + (process.env.EMAIL_LINK_FORGOTPASSWORD_PATH ?? '/forgot-password'),
EMAIL_TLS: process.env.EMAIL_TLS !== 'false',
EMAIL_TEST_MODUS: process.env.EMAIL_TEST_MODUS === 'true',
EMAIL_TEST_RECEIVER: process.env.EMAIL_TEST_RECEIVER ?? 'stage1@gradido.net',
EMAIL_USERNAME: process.env.EMAIL_USERNAME ?? '',
EMAIL_SENDER: process.env.EMAIL_SENDER ?? 'info@gradido.net',
EMAIL_PASSWORD: process.env.EMAIL_PASSWORD ?? '',
EMAIL_SMTP_HOST: process.env.EMAIL_SMTP_HOST ?? 'mailserver',
EMAIL_SMTP_PORT: Number(process.env.EMAIL_SMTP_PORT) || 1025,
}
export const CONFIG = {
...federation,
...community,
...email,
}
validate(schema, CONFIG)

View File

@ -1,6 +1,93 @@
import Joi from 'joi'
import { COMMUNITY_SUPPORT_MAIL, COMMUNITY_URL, NODE_ENV } from 'config-schema'
export const schema = Joi.object({
COMMUNITY_SUPPORT_MAIL,
COMMUNITY_URL,
NODE_ENV,
EMAIL: Joi.boolean()
.default(false)
.description('Enable or disable email functionality')
.required(),
EMAIL_LINK_FORGOTPASSWORD: Joi.string()
.uri({ scheme: ['http', 'https'] })
.custom((value: string, helpers: Joi.CustomHelpers<string>): string | Joi.ErrorReport => {
if (!value.startsWith(helpers.state.ancestors[0].COMMUNITY_URL)) {
return helpers.error('string.pattern.base', { value, communityUrl: COMMUNITY_URL })
}
return value
})
.description('Email Verification link for set new Password, when old Password was forgotten.')
.required(),
EMAIL_TEST_MODUS: Joi.boolean()
.default(false)
.description('When enabled, all emails are sended to EMAIL_TEST_RECEIVER')
.optional(),
EMAIL_TEST_RECEIVER: Joi.string()
.email()
.default('stage1@gradido.net')
.when('EMAIL_TEST_MODUS', { is: true, then: Joi.required() })
.description('Email address used in test mode'),
EMAIL_USERNAME: Joi.alternatives().conditional(Joi.ref('EMAIL'), {
is: true,
then: Joi.alternatives().conditional(Joi.ref('NODE_ENV'), {
is: 'development',
then: Joi.string()
.allow('')
.description('Username for SMTP authentication (optional in development)'),
otherwise: Joi.string()
.pattern(/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/)
.description('Valid SMTP username required in production')
.required(),
}),
otherwise: Joi.string().allow('').optional(),
}),
EMAIL_SENDER: Joi.string()
.email()
.when('EMAIL', { is: true, then: Joi.required() })
.default('info@gradido.net')
.description('Email address used as sender'),
EMAIL_PASSWORD: Joi.alternatives().conditional(Joi.ref('EMAIL'), {
is: true,
then: Joi.alternatives().conditional(Joi.ref('NODE_ENV'), {
is: 'development',
then: Joi.string()
.allow('')
.description('Password for SMTP authentication (optional in development)'),
otherwise: Joi.string()
.min(8)
.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&#]).{8,}$/)
.description(
'Password must be at least 8 characters long, include uppercase and lowercase letters, a number, and a special character',
)
.required(),
}),
otherwise: Joi.string().allow('').optional(),
}),
EMAIL_SMTP_HOST: Joi.string()
.hostname()
.when('EMAIL', { is: true, then: Joi.required() })
.default('mailserver')
.description('SMTP server hostname'),
EMAIL_SMTP_PORT: Joi.number()
.integer()
.positive()
.when('EMAIL', { is: true, then: Joi.required() })
.default(1025)
.description('SMTP server port'),
EMAIL_TLS: Joi.boolean().default(true).description('Enable or disable TLS for SMTP').optional(),
FEDERATION_BACKEND_SEND_ON_API: Joi.string()
.pattern(/^\d+_\d+$/)
.default('1_0')

View File

@ -2,12 +2,12 @@
exports[`sendEmailVariants sendAccountActivationEmail result has the correct html as snapshot 1`] = `
"<!DOCTYPE html>
<html lang=\\"en\\">
<html lang=\"en\">
<head>
<meta content=\\"multipart/html; charset=UTF-8\\" http-equiv=\\"content-type\\">
<meta name=\\"viewport\\" content=\\"width=device-width, initial-scale=1\\">
<meta content=\"multipart/html; charset=UTF-8\" http-equiv=\"content-type\">
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">
<style>
.wf-force-outline-none[tabindex=\\"-1\\"]:focus {
.wf-force-outline-none[tabindex=\"-1\"]:focus {
outline: none;
}
</style>
@ -133,41 +133,41 @@ exports[`sendEmailVariants sendAccountActivationEmail result has the correct htm
}
</style>
</head>
<body style=\\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\\">
<div class=\\"container\\" style=\\"max-width: 680px; margin: 0 auto; display: block;\\">
<body style=\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\">
<div class=\"container\" style=\"max-width: 680px; margin: 0 auto; display: block;\">
<header>
<div class=\\"head\\"><img class=\\"head-logo\\" alt=\\"Gradido Logo\\" loading=\\"lazy\\" src=\\"cid:gradidoheader\\" style=\\"width: 100%; height: auto;\\"></div>
<div class=\"head\"><img class=\"head-logo\" alt=\"Gradido Logo\" loading=\"lazy\" src=\"cid:gradidoheader\" style=\"width: 100%; height: auto;\"></div>
</header>
<div class=\\"wrapper\\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Email Verification</h2>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\">
<div class=\"wrapper\">
<h2 style=\"margin-top: 15px; color: #383838;\">Email Verification</h2>
<div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Hello Peter Lustig,</p>
<p>Your email address has just been registered with Gradido.</p>
</div>
<div class=\\"content\\" style=\\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Complete registration</h2>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Please click here to complete the registration and activate your Gradido account.</div><a class=\\"button-3\\" href=\\"http://localhost/checkEmail/6627633878930542284\\" style=\\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\\">Activate account</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Or copy the link into your browser window.</div><a class=\\"clink\\" href=\\"http://localhost/checkEmail/6627633878930542284\\" style=\\"line-break: anywhere; margin-bottom: 40px;\\">http://localhost/checkEmail/6627633878930542284</a>
<div class=\"content\" style=\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\">
<h2 style=\"margin-top: 15px; color: #383838;\">Complete registration</h2>
<div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">Please click here to complete the registration and activate your Gradido account.</div><a class=\"button-3\" href=\"http://localhost/checkEmail/6627633878930542284\" style=\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\">Activate account</a>
<div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">Or copy the link into your browser window.</div><a class=\"clink\" href=\"http://localhost/checkEmail/6627633878930542284\" style=\"line-break: anywhere; margin-bottom: 40px;\">http://localhost/checkEmail/6627633878930542284</a>
<requestnewlink>
<h2 style=\\"margin-top: 15px; color: #383838;\\">Request new valid link</h2>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">The link has a validity of 23 hours and 30 minutes.
If the validity of the link has already expired, you can have a new link sent to you here.</div><a class=\\"button-4\\" href=\\"http://localhost/forgot-password\\" style=\\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%; background-image: radial-gradient(circle farthest-corner at 0% 0%, #616161, #c2c2c2);\\">New link</a>
<h2 style=\"margin-top: 15px; color: #383838;\">Request new valid link</h2>
<div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">The link has a validity of 23 hours and 30 minutes.
If the validity of the link has already expired, you can have a new link sent to you here.</div><a class=\"button-4\" href=\"http://localhost/forgot-password\" style=\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%; background-image: radial-gradient(circle farthest-corner at 0% 0%, #616161, #c2c2c2);\">New link</a>
</requestnewlink>
</div>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\">
<div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Kind regards,<br>your Gradido team
</p>
</div>
</div>
<footer>
<div class=\\"w-container footer_01\\">
<div class=\\"socialmedia\\" style=\\"display: flex; margin-top: 40px; max-width: 600px;\\"><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.facebook.com/groups/Gradido/\\" style=\\"width: 150px;\\"><img class=\\"bi-facebook\\" alt=\\"facebook\\" loading=\\"lazy\\" src=\\"cid:facebookicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://t.me/GradidoGruppe\\" style=\\"width: 150px;\\"><img class=\\"bi-telegram\\" alt=\\"Telegram\\" loading=\\"lazy\\" src=\\"cid:telegramicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://twitter.com/gradido\\" style=\\"width: 150px;\\"><img class=\\"bi-twitter\\" alt=\\"Twitter\\" loading=\\"lazy\\" src=\\"cid:twittericon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.youtube.com/c/GradidoNet\\" style=\\"width: 150px;\\"><img class=\\"bi-youtube\\" alt=\\"youtube\\" loading=\\"lazy\\" src=\\"cid:youtubeicon\\"></a></div>
<div class=\\"line\\" style=\\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\\"></div>
<div class=\\"footer\\" style=\\"padding-bottom: 20px;\\">
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">If you have any further questions, please contact our support.</div><a class=\\"footer_p2\\" href=\\"mailto:support@gradido.net\\" style=\\"color: #383838; font-weight: bold;\\">support@gradido.net</a>
<div> <img class=\\"image\\" alt=\\"Gradido Logo\\" src=\\"https://gdd.gradido.net/img/brand/green.png\\" style=\\"width: 200px; margin-top: 30px; margin-bottom: 30px;\\" width=\\"200\\"></div>
<div><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/impressum/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Impressum</a></div><br><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/datenschutz/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Privacy Policy</a>
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div>
<div class=\"w-container footer_01\">
<div class=\"socialmedia\" style=\"display: flex; margin-top: 40px; max-width: 600px;\"><a class=\"slink\" target=\"_blank\" href=\"https://www.facebook.com/groups/Gradido/\" style=\"width: 150px;\"><img class=\"bi-facebook\" alt=\"facebook\" loading=\"lazy\" src=\"cid:facebookicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://t.me/GradidoGruppe\" style=\"width: 150px;\"><img class=\"bi-telegram\" alt=\"Telegram\" loading=\"lazy\" src=\"cid:telegramicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://twitter.com/gradido\" style=\"width: 150px;\"><img class=\"bi-twitter\" alt=\"Twitter\" loading=\"lazy\" src=\"cid:twittericon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://www.youtube.com/c/GradidoNet\" style=\"width: 150px;\"><img class=\"bi-youtube\" alt=\"youtube\" loading=\"lazy\" src=\"cid:youtubeicon\"></a></div>
<div class=\"line\" style=\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\"></div>
<div class=\"footer\" style=\"padding-bottom: 20px;\">
<div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">If you have any further questions, please contact our support.</div><a class=\"footer_p2\" href=\"mailto:support@gradido.net\" style=\"color: #383838; font-weight: bold;\">support@gradido.net</a>
<div> <img class=\"image\" alt=\"Gradido Logo\" src=\"https://gdd.gradido.net/img/brand/green.png\" style=\"width: 200px; margin-top: 30px; margin-bottom: 30px;\" width=\"200\"></div>
<div><a class=\"terms_of_use\" href=\"https://gradido.net/de/impressum/\" target=\"_blank\" style=\"color: #9ca0a8;\">Impressum</a></div><br><a class=\"terms_of_use\" href=\"https://gradido.net/de/datenschutz/\" target=\"_blank\" style=\"color: #9ca0a8;\">Privacy Policy</a>
<div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div>
</div>
</div>
</footer>
@ -178,12 +178,12 @@ If the validity of the link has already expired, you can have a new link sent to
exports[`sendEmailVariants sendAccountMultiRegistrationEmail calls "sendEmailTranslated" result has the correct html as snapshot 1`] = `
"<!DOCTYPE html>
<html lang=\\"en\\">
<html lang=\"en\">
<head>
<meta content=\\"multipart/html; charset=UTF-8\\" http-equiv=\\"content-type\\">
<meta name=\\"viewport\\" content=\\"width=device-width, initial-scale=1\\">
<meta content=\"multipart/html; charset=UTF-8\" http-equiv=\"content-type\">
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">
<style>
.wf-force-outline-none[tabindex=\\"-1\\"]:focus {
.wf-force-outline-none[tabindex=\"-1\"]:focus {
outline: none;
}
</style>
@ -309,39 +309,39 @@ exports[`sendEmailVariants sendAccountMultiRegistrationEmail calls "sendEmailTra
}
</style>
</head>
<body style=\\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\\">
<div class=\\"container\\" style=\\"max-width: 680px; margin: 0 auto; display: block;\\">
<body style=\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\">
<div class=\"container\" style=\"max-width: 680px; margin: 0 auto; display: block;\">
<header>
<div class=\\"head\\"><img class=\\"head-logo\\" alt=\\"Gradido Logo\\" loading=\\"lazy\\" src=\\"cid:gradidoheader\\" style=\\"width: 100%; height: auto;\\"></div>
<div class=\"head\"><img class=\"head-logo\" alt=\"Gradido Logo\" loading=\"lazy\" src=\"cid:gradidoheader\" style=\"width: 100%; height: auto;\"></div>
</header>
<div class=\\"wrapper\\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Try To Register Again With Your Email</h2>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\">
<div class=\"wrapper\">
<h2 style=\"margin-top: 15px; color: #383838;\">Try To Register Again With Your Email</h2>
<div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Hello Peter Lustig,</p>
<p>Your email address has just been used again to register an account with Gradido.<br>However, an account already exists for your email address.
</p>
</div>
<div class=\\"content\\" style=\\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Reset password</h2>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">If you have forgotten your password, please click here.</div><a class=\\"button-3\\" href=\\"http://localhost/forgot-password\\" style=\\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\\">reset</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Or copy the link into your browser window.</div><a class=\\"clink\\" href=\\"http://localhost/forgot-password\\" style=\\"line-break: anywhere; margin-bottom: 40px;\\">http://localhost/forgot-password</a>
<h2 style=\\"margin-top: 15px; color: red;\\">Contact support</h2>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">If you did not try to register again, please contact our support:</div><a class=\\"clink\\" href=\\"mailto:support@supportmail.com\\" style=\\"line-break: anywhere; margin-bottom: 40px;\\">support@supportmail.com</a>
<div class=\"content\" style=\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\">
<h2 style=\"margin-top: 15px; color: #383838;\">Reset password</h2>
<div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">If you have forgotten your password, please click here.</div><a class=\"button-3\" href=\"http://localhost/forgot-password\" style=\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\">reset</a>
<div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">Or copy the link into your browser window.</div><a class=\"clink\" href=\"http://localhost/forgot-password\" style=\"line-break: anywhere; margin-bottom: 40px;\">http://localhost/forgot-password</a>
<h2 style=\"margin-top: 15px; color: red;\">Contact support</h2>
<div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">If you did not try to register again, please contact our support:</div><a class=\"clink\" href=\"mailto:support@supportmail.com\" style=\"line-break: anywhere; margin-bottom: 40px;\">support@supportmail.com</a>
</div>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\">
<div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Kind regards,<br>your Gradido team
</p>
</div>
</div>
<footer>
<div class=\\"w-container footer_01\\">
<div class=\\"socialmedia\\" style=\\"display: flex; margin-top: 40px; max-width: 600px;\\"><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.facebook.com/groups/Gradido/\\" style=\\"width: 150px;\\"><img class=\\"bi-facebook\\" alt=\\"facebook\\" loading=\\"lazy\\" src=\\"cid:facebookicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://t.me/GradidoGruppe\\" style=\\"width: 150px;\\"><img class=\\"bi-telegram\\" alt=\\"Telegram\\" loading=\\"lazy\\" src=\\"cid:telegramicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://twitter.com/gradido\\" style=\\"width: 150px;\\"><img class=\\"bi-twitter\\" alt=\\"Twitter\\" loading=\\"lazy\\" src=\\"cid:twittericon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.youtube.com/c/GradidoNet\\" style=\\"width: 150px;\\"><img class=\\"bi-youtube\\" alt=\\"youtube\\" loading=\\"lazy\\" src=\\"cid:youtubeicon\\"></a></div>
<div class=\\"line\\" style=\\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\\"></div>
<div class=\\"footer\\" style=\\"padding-bottom: 20px;\\">
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">If you have any further questions, please contact our support.</div><a class=\\"footer_p2\\" href=\\"mailto:support@gradido.net\\" style=\\"color: #383838; font-weight: bold;\\">support@gradido.net</a>
<div> <img class=\\"image\\" alt=\\"Gradido Logo\\" src=\\"https://gdd.gradido.net/img/brand/green.png\\" style=\\"width: 200px; margin-top: 30px; margin-bottom: 30px;\\" width=\\"200\\"></div>
<div><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/impressum/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Impressum</a></div><br><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/datenschutz/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Privacy Policy</a>
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div>
<div class=\"w-container footer_01\">
<div class=\"socialmedia\" style=\"display: flex; margin-top: 40px; max-width: 600px;\"><a class=\"slink\" target=\"_blank\" href=\"https://www.facebook.com/groups/Gradido/\" style=\"width: 150px;\"><img class=\"bi-facebook\" alt=\"facebook\" loading=\"lazy\" src=\"cid:facebookicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://t.me/GradidoGruppe\" style=\"width: 150px;\"><img class=\"bi-telegram\" alt=\"Telegram\" loading=\"lazy\" src=\"cid:telegramicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://twitter.com/gradido\" style=\"width: 150px;\"><img class=\"bi-twitter\" alt=\"Twitter\" loading=\"lazy\" src=\"cid:twittericon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://www.youtube.com/c/GradidoNet\" style=\"width: 150px;\"><img class=\"bi-youtube\" alt=\"youtube\" loading=\"lazy\" src=\"cid:youtubeicon\"></a></div>
<div class=\"line\" style=\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\"></div>
<div class=\"footer\" style=\"padding-bottom: 20px;\">
<div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">If you have any further questions, please contact our support.</div><a class=\"footer_p2\" href=\"mailto:support@gradido.net\" style=\"color: #383838; font-weight: bold;\">support@gradido.net</a>
<div> <img class=\"image\" alt=\"Gradido Logo\" src=\"https://gdd.gradido.net/img/brand/green.png\" style=\"width: 200px; margin-top: 30px; margin-bottom: 30px;\" width=\"200\"></div>
<div><a class=\"terms_of_use\" href=\"https://gradido.net/de/impressum/\" target=\"_blank\" style=\"color: #9ca0a8;\">Impressum</a></div><br><a class=\"terms_of_use\" href=\"https://gradido.net/de/datenschutz/\" target=\"_blank\" style=\"color: #9ca0a8;\">Privacy Policy</a>
<div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div>
</div>
</div>
</footer>
@ -352,12 +352,12 @@ exports[`sendEmailVariants sendAccountMultiRegistrationEmail calls "sendEmailTra
exports[`sendEmailVariants sendAddedContributionMessageEmail result has the correct html as snapshot 1`] = `
"<!DOCTYPE html>
<html lang=\\"en\\">
<html lang=\"en\">
<head>
<meta content=\\"multipart/html; charset=UTF-8\\" http-equiv=\\"content-type\\">
<meta name=\\"viewport\\" content=\\"width=device-width, initial-scale=1\\">
<meta content=\"multipart/html; charset=UTF-8\" http-equiv=\"content-type\">
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">
<style>
.wf-force-outline-none[tabindex=\\"-1\\"]:focus {
.wf-force-outline-none[tabindex=\"-1\"]:focus {
outline: none;
}
</style>
@ -483,39 +483,39 @@ exports[`sendEmailVariants sendAddedContributionMessageEmail result has the corr
}
</style>
</head>
<body style=\\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\\">
<div class=\\"container\\" style=\\"max-width: 680px; margin: 0 auto; display: block;\\">
<body style=\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\">
<div class=\"container\" style=\"max-width: 680px; margin: 0 auto; display: block;\">
<header>
<div class=\\"head\\"><img class=\\"head-logo\\" alt=\\"Gradido Logo\\" loading=\\"lazy\\" src=\\"cid:gradidoheader\\" style=\\"width: 100%; height: auto;\\"></div>
<div class=\"head\"><img class=\"head-logo\" alt=\"Gradido Logo\" loading=\"lazy\" src=\"cid:gradidoheader\" style=\"width: 100%; height: auto;\"></div>
</header>
<div class=\\"wrapper\\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Message about your common good contribution</h2>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\">
<div class=\"wrapper\">
<h2 style=\"margin-top: 15px; color: #383838;\">Message about your common good contribution</h2>
<div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Hello Peter Lustig,</p>
<p>You have received a message from Bibi Bloxberg regarding your common good contribution “My contribution.”.</p>
</div>
<div class=\\"content\\" style=\\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Read and reply to message</h2>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">
<div class=\"content\" style=\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\">
<h2 style=\"margin-top: 15px; color: #383838;\">Read and reply to message</h2>
<div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">
<p>„My message.“</p>
<p>To reply to the message, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab.</p>
</div><a class=\\"button-3\\" href=\\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\\" style=\\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\\">To account</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Please do not reply to this email.</div>
</div><a class=\"button-3\" href=\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\" style=\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\">To account</a>
<div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">Please do not reply to this email.</div>
</div>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\">
<div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Kind regards,<br>your Gradido team
</p>
</div>
</div>
<footer>
<div class=\\"w-container footer_01\\">
<div class=\\"socialmedia\\" style=\\"display: flex; margin-top: 40px; max-width: 600px;\\"><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.facebook.com/groups/Gradido/\\" style=\\"width: 150px;\\"><img class=\\"bi-facebook\\" alt=\\"facebook\\" loading=\\"lazy\\" src=\\"cid:facebookicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://t.me/GradidoGruppe\\" style=\\"width: 150px;\\"><img class=\\"bi-telegram\\" alt=\\"Telegram\\" loading=\\"lazy\\" src=\\"cid:telegramicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://twitter.com/gradido\\" style=\\"width: 150px;\\"><img class=\\"bi-twitter\\" alt=\\"Twitter\\" loading=\\"lazy\\" src=\\"cid:twittericon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.youtube.com/c/GradidoNet\\" style=\\"width: 150px;\\"><img class=\\"bi-youtube\\" alt=\\"youtube\\" loading=\\"lazy\\" src=\\"cid:youtubeicon\\"></a></div>
<div class=\\"line\\" style=\\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\\"></div>
<div class=\\"footer\\" style=\\"padding-bottom: 20px;\\">
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">If you have any further questions, please contact our support.</div><a class=\\"footer_p2\\" href=\\"mailto:support@gradido.net\\" style=\\"color: #383838; font-weight: bold;\\">support@gradido.net</a>
<div> <img class=\\"image\\" alt=\\"Gradido Logo\\" src=\\"https://gdd.gradido.net/img/brand/green.png\\" style=\\"width: 200px; margin-top: 30px; margin-bottom: 30px;\\" width=\\"200\\"></div>
<div><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/impressum/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Impressum</a></div><br><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/datenschutz/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Privacy Policy</a>
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div>
<div class=\"w-container footer_01\">
<div class=\"socialmedia\" style=\"display: flex; margin-top: 40px; max-width: 600px;\"><a class=\"slink\" target=\"_blank\" href=\"https://www.facebook.com/groups/Gradido/\" style=\"width: 150px;\"><img class=\"bi-facebook\" alt=\"facebook\" loading=\"lazy\" src=\"cid:facebookicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://t.me/GradidoGruppe\" style=\"width: 150px;\"><img class=\"bi-telegram\" alt=\"Telegram\" loading=\"lazy\" src=\"cid:telegramicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://twitter.com/gradido\" style=\"width: 150px;\"><img class=\"bi-twitter\" alt=\"Twitter\" loading=\"lazy\" src=\"cid:twittericon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://www.youtube.com/c/GradidoNet\" style=\"width: 150px;\"><img class=\"bi-youtube\" alt=\"youtube\" loading=\"lazy\" src=\"cid:youtubeicon\"></a></div>
<div class=\"line\" style=\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\"></div>
<div class=\"footer\" style=\"padding-bottom: 20px;\">
<div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">If you have any further questions, please contact our support.</div><a class=\"footer_p2\" href=\"mailto:support@gradido.net\" style=\"color: #383838; font-weight: bold;\">support@gradido.net</a>
<div> <img class=\"image\" alt=\"Gradido Logo\" src=\"https://gdd.gradido.net/img/brand/green.png\" style=\"width: 200px; margin-top: 30px; margin-bottom: 30px;\" width=\"200\"></div>
<div><a class=\"terms_of_use\" href=\"https://gradido.net/de/impressum/\" target=\"_blank\" style=\"color: #9ca0a8;\">Impressum</a></div><br><a class=\"terms_of_use\" href=\"https://gradido.net/de/datenschutz/\" target=\"_blank\" style=\"color: #9ca0a8;\">Privacy Policy</a>
<div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div>
</div>
</div>
</footer>
@ -526,12 +526,12 @@ exports[`sendEmailVariants sendAddedContributionMessageEmail result has the corr
exports[`sendEmailVariants sendContributionChangedByModeratorEmail result has the correct html as snapshot 1`] = `
"<!DOCTYPE html>
<html lang=\\"en\\">
<html lang=\"en\">
<head>
<meta content=\\"multipart/html; charset=UTF-8\\" http-equiv=\\"content-type\\">
<meta name=\\"viewport\\" content=\\"width=device-width, initial-scale=1\\">
<meta content=\"multipart/html; charset=UTF-8\" http-equiv=\"content-type\">
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">
<style>
.wf-force-outline-none[tabindex=\\"-1\\"]:focus {
.wf-force-outline-none[tabindex=\"-1\"]:focus {
outline: none;
}
</style>
@ -657,37 +657,37 @@ exports[`sendEmailVariants sendContributionChangedByModeratorEmail result has th
}
</style>
</head>
<body style=\\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\\">
<div class=\\"container\\" style=\\"max-width: 680px; margin: 0 auto; display: block;\\">
<body style=\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\">
<div class=\"container\" style=\"max-width: 680px; margin: 0 auto; display: block;\">
<header>
<div class=\\"head\\"><img class=\\"head-logo\\" alt=\\"Gradido Logo\\" loading=\\"lazy\\" src=\\"cid:gradidoheader\\" style=\\"width: 100%; height: auto;\\"></div>
<div class=\"head\"><img class=\"head-logo\" alt=\"Gradido Logo\" loading=\"lazy\" src=\"cid:gradidoheader\" style=\"width: 100%; height: auto;\"></div>
</header>
<div class=\\"wrapper\\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Your common good contribution has been changed</h2>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\">
<div class=\"wrapper\">
<h2 style=\"margin-top: 15px; color: #383838;\">Your common good contribution has been changed</h2>
<div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Hello Peter Lustig,</p>
<p>your common good contribution 'My contribution.' has just been changed by Bibi Bloxberg and now reads as 'This is a better contribution memo.'</p>
</div>
<div class=\\"content\\" style=\\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Contribution details</h2>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab.</div><a class=\\"button-3\\" href=\\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\\" style=\\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\\">To account</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Or copy the link into your browser window.</div><a class=\\"clink\\" href=\\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\\" style=\\"line-break: anywhere; margin-bottom: 40px;\\">https://gradido.net/contributions/own-contributions/1#contributionListItem-1</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Please do not reply to this email.</div>
<div class=\"content\" style=\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\">
<h2 style=\"margin-top: 15px; color: #383838;\">Contribution details</h2>
<div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab.</div><a class=\"button-3\" href=\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\" style=\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\">To account</a>
<div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">Or copy the link into your browser window.</div><a class=\"clink\" href=\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\" style=\"line-break: anywhere; margin-bottom: 40px;\">https://gradido.net/contributions/own-contributions/1#contributionListItem-1</a>
<div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">Please do not reply to this email.</div>
</div>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\">
<div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Kind regards,<br>your Gradido team
</p>
</div>
</div>
<footer>
<div class=\\"w-container footer_01\\">
<div class=\\"socialmedia\\" style=\\"display: flex; margin-top: 40px; max-width: 600px;\\"><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.facebook.com/groups/Gradido/\\" style=\\"width: 150px;\\"><img class=\\"bi-facebook\\" alt=\\"facebook\\" loading=\\"lazy\\" src=\\"cid:facebookicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://t.me/GradidoGruppe\\" style=\\"width: 150px;\\"><img class=\\"bi-telegram\\" alt=\\"Telegram\\" loading=\\"lazy\\" src=\\"cid:telegramicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://twitter.com/gradido\\" style=\\"width: 150px;\\"><img class=\\"bi-twitter\\" alt=\\"Twitter\\" loading=\\"lazy\\" src=\\"cid:twittericon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.youtube.com/c/GradidoNet\\" style=\\"width: 150px;\\"><img class=\\"bi-youtube\\" alt=\\"youtube\\" loading=\\"lazy\\" src=\\"cid:youtubeicon\\"></a></div>
<div class=\\"line\\" style=\\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\\"></div>
<div class=\\"footer\\" style=\\"padding-bottom: 20px;\\">
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">If you have any further questions, please contact our support.</div><a class=\\"footer_p2\\" href=\\"mailto:support@gradido.net\\" style=\\"color: #383838; font-weight: bold;\\">support@gradido.net</a>
<div> <img class=\\"image\\" alt=\\"Gradido Logo\\" src=\\"https://gdd.gradido.net/img/brand/green.png\\" style=\\"width: 200px; margin-top: 30px; margin-bottom: 30px;\\" width=\\"200\\"></div>
<div><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/impressum/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Impressum</a></div><br><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/datenschutz/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Privacy Policy</a>
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div>
<div class=\"w-container footer_01\">
<div class=\"socialmedia\" style=\"display: flex; margin-top: 40px; max-width: 600px;\"><a class=\"slink\" target=\"_blank\" href=\"https://www.facebook.com/groups/Gradido/\" style=\"width: 150px;\"><img class=\"bi-facebook\" alt=\"facebook\" loading=\"lazy\" src=\"cid:facebookicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://t.me/GradidoGruppe\" style=\"width: 150px;\"><img class=\"bi-telegram\" alt=\"Telegram\" loading=\"lazy\" src=\"cid:telegramicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://twitter.com/gradido\" style=\"width: 150px;\"><img class=\"bi-twitter\" alt=\"Twitter\" loading=\"lazy\" src=\"cid:twittericon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://www.youtube.com/c/GradidoNet\" style=\"width: 150px;\"><img class=\"bi-youtube\" alt=\"youtube\" loading=\"lazy\" src=\"cid:youtubeicon\"></a></div>
<div class=\"line\" style=\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\"></div>
<div class=\"footer\" style=\"padding-bottom: 20px;\">
<div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">If you have any further questions, please contact our support.</div><a class=\"footer_p2\" href=\"mailto:support@gradido.net\" style=\"color: #383838; font-weight: bold;\">support@gradido.net</a>
<div> <img class=\"image\" alt=\"Gradido Logo\" src=\"https://gdd.gradido.net/img/brand/green.png\" style=\"width: 200px; margin-top: 30px; margin-bottom: 30px;\" width=\"200\"></div>
<div><a class=\"terms_of_use\" href=\"https://gradido.net/de/impressum/\" target=\"_blank\" style=\"color: #9ca0a8;\">Impressum</a></div><br><a class=\"terms_of_use\" href=\"https://gradido.net/de/datenschutz/\" target=\"_blank\" style=\"color: #9ca0a8;\">Privacy Policy</a>
<div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div>
</div>
</div>
</footer>
@ -698,12 +698,12 @@ exports[`sendEmailVariants sendContributionChangedByModeratorEmail result has th
exports[`sendEmailVariants sendContributionConfirmedEmail result has the correct html as snapshot 1`] = `
"<!DOCTYPE html>
<html lang=\\"en\\">
<html lang=\"en\">
<head>
<meta content=\\"multipart/html; charset=UTF-8\\" http-equiv=\\"content-type\\">
<meta name=\\"viewport\\" content=\\"width=device-width, initial-scale=1\\">
<meta content=\"multipart/html; charset=UTF-8\" http-equiv=\"content-type\">
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">
<style>
.wf-force-outline-none[tabindex=\\"-1\\"]:focus {
.wf-force-outline-none[tabindex=\"-1\"]:focus {
outline: none;
}
</style>
@ -829,37 +829,37 @@ exports[`sendEmailVariants sendContributionConfirmedEmail result has the correct
}
</style>
</head>
<body style=\\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\\">
<div class=\\"container\\" style=\\"max-width: 680px; margin: 0 auto; display: block;\\">
<body style=\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\">
<div class=\"container\" style=\"max-width: 680px; margin: 0 auto; display: block;\">
<header>
<div class=\\"head\\"><img class=\\"head-logo\\" alt=\\"Gradido Logo\\" loading=\\"lazy\\" src=\\"cid:gradidoheader\\" style=\\"width: 100%; height: auto;\\"></div>
<div class=\"head\"><img class=\"head-logo\" alt=\"Gradido Logo\" loading=\"lazy\" src=\"cid:gradidoheader\" style=\"width: 100%; height: auto;\"></div>
</header>
<div class=\\"wrapper\\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Your contribution to the common good was confirmed</h2>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\">
<div class=\"wrapper\">
<h2 style=\"margin-top: 15px; color: #383838;\">Your contribution to the common good was confirmed</h2>
<div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Hello Peter Lustig,</p>
<p>Your common good contribution “My contribution.” has just been approved by Bibi Bloxberg. Your Gradido account has been credited with 23.54 GDD.</p>
</div>
<div class=\\"content\\" style=\\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Contribution details</h2>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab.</div><a class=\\"button-3\\" href=\\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\\" style=\\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\\">To account</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Or copy the link into your browser window.</div><a class=\\"clink\\" href=\\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\\" style=\\"line-break: anywhere; margin-bottom: 40px;\\">https://gradido.net/contributions/own-contributions/1#contributionListItem-1</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Please do not reply to this email.</div>
<div class=\"content\" style=\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\">
<h2 style=\"margin-top: 15px; color: #383838;\">Contribution details</h2>
<div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab.</div><a class=\"button-3\" href=\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\" style=\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\">To account</a>
<div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">Or copy the link into your browser window.</div><a class=\"clink\" href=\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\" style=\"line-break: anywhere; margin-bottom: 40px;\">https://gradido.net/contributions/own-contributions/1#contributionListItem-1</a>
<div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">Please do not reply to this email.</div>
</div>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\">
<div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Kind regards,<br>your Gradido team
</p>
</div>
</div>
<footer>
<div class=\\"w-container footer_01\\">
<div class=\\"socialmedia\\" style=\\"display: flex; margin-top: 40px; max-width: 600px;\\"><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.facebook.com/groups/Gradido/\\" style=\\"width: 150px;\\"><img class=\\"bi-facebook\\" alt=\\"facebook\\" loading=\\"lazy\\" src=\\"cid:facebookicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://t.me/GradidoGruppe\\" style=\\"width: 150px;\\"><img class=\\"bi-telegram\\" alt=\\"Telegram\\" loading=\\"lazy\\" src=\\"cid:telegramicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://twitter.com/gradido\\" style=\\"width: 150px;\\"><img class=\\"bi-twitter\\" alt=\\"Twitter\\" loading=\\"lazy\\" src=\\"cid:twittericon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.youtube.com/c/GradidoNet\\" style=\\"width: 150px;\\"><img class=\\"bi-youtube\\" alt=\\"youtube\\" loading=\\"lazy\\" src=\\"cid:youtubeicon\\"></a></div>
<div class=\\"line\\" style=\\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\\"></div>
<div class=\\"footer\\" style=\\"padding-bottom: 20px;\\">
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">If you have any further questions, please contact our support.</div><a class=\\"footer_p2\\" href=\\"mailto:support@gradido.net\\" style=\\"color: #383838; font-weight: bold;\\">support@gradido.net</a>
<div> <img class=\\"image\\" alt=\\"Gradido Logo\\" src=\\"https://gdd.gradido.net/img/brand/green.png\\" style=\\"width: 200px; margin-top: 30px; margin-bottom: 30px;\\" width=\\"200\\"></div>
<div><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/impressum/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Impressum</a></div><br><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/datenschutz/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Privacy Policy</a>
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div>
<div class=\"w-container footer_01\">
<div class=\"socialmedia\" style=\"display: flex; margin-top: 40px; max-width: 600px;\"><a class=\"slink\" target=\"_blank\" href=\"https://www.facebook.com/groups/Gradido/\" style=\"width: 150px;\"><img class=\"bi-facebook\" alt=\"facebook\" loading=\"lazy\" src=\"cid:facebookicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://t.me/GradidoGruppe\" style=\"width: 150px;\"><img class=\"bi-telegram\" alt=\"Telegram\" loading=\"lazy\" src=\"cid:telegramicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://twitter.com/gradido\" style=\"width: 150px;\"><img class=\"bi-twitter\" alt=\"Twitter\" loading=\"lazy\" src=\"cid:twittericon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://www.youtube.com/c/GradidoNet\" style=\"width: 150px;\"><img class=\"bi-youtube\" alt=\"youtube\" loading=\"lazy\" src=\"cid:youtubeicon\"></a></div>
<div class=\"line\" style=\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\"></div>
<div class=\"footer\" style=\"padding-bottom: 20px;\">
<div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">If you have any further questions, please contact our support.</div><a class=\"footer_p2\" href=\"mailto:support@gradido.net\" style=\"color: #383838; font-weight: bold;\">support@gradido.net</a>
<div> <img class=\"image\" alt=\"Gradido Logo\" src=\"https://gdd.gradido.net/img/brand/green.png\" style=\"width: 200px; margin-top: 30px; margin-bottom: 30px;\" width=\"200\"></div>
<div><a class=\"terms_of_use\" href=\"https://gradido.net/de/impressum/\" target=\"_blank\" style=\"color: #9ca0a8;\">Impressum</a></div><br><a class=\"terms_of_use\" href=\"https://gradido.net/de/datenschutz/\" target=\"_blank\" style=\"color: #9ca0a8;\">Privacy Policy</a>
<div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div>
</div>
</div>
</footer>
@ -870,12 +870,12 @@ exports[`sendEmailVariants sendContributionConfirmedEmail result has the correct
exports[`sendEmailVariants sendContributionDeletedEmail result has the correct html as snapshot 1`] = `
"<!DOCTYPE html>
<html lang=\\"en\\">
<html lang=\"en\">
<head>
<meta content=\\"multipart/html; charset=UTF-8\\" http-equiv=\\"content-type\\">
<meta name=\\"viewport\\" content=\\"width=device-width, initial-scale=1\\">
<meta content=\"multipart/html; charset=UTF-8\" http-equiv=\"content-type\">
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">
<style>
.wf-force-outline-none[tabindex=\\"-1\\"]:focus {
.wf-force-outline-none[tabindex=\"-1\"]:focus {
outline: none;
}
</style>
@ -1001,37 +1001,37 @@ exports[`sendEmailVariants sendContributionDeletedEmail result has the correct h
}
</style>
</head>
<body style=\\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\\">
<div class=\\"container\\" style=\\"max-width: 680px; margin: 0 auto; display: block;\\">
<body style=\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\">
<div class=\"container\" style=\"max-width: 680px; margin: 0 auto; display: block;\">
<header>
<div class=\\"head\\"><img class=\\"head-logo\\" alt=\\"Gradido Logo\\" loading=\\"lazy\\" src=\\"cid:gradidoheader\\" style=\\"width: 100%; height: auto;\\"></div>
<div class=\"head\"><img class=\"head-logo\" alt=\"Gradido Logo\" loading=\"lazy\" src=\"cid:gradidoheader\" style=\"width: 100%; height: auto;\"></div>
</header>
<div class=\\"wrapper\\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Your common good contribution was deleted</h2>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\">
<div class=\"wrapper\">
<h2 style=\"margin-top: 15px; color: #383838;\">Your common good contribution was deleted</h2>
<div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Hello Peter Lustig,</p>
<p>Your common good contribution “My contribution.” was deleted by Bibi Bloxberg.</p>
</div>
<div class=\\"content\\" style=\\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Contribution details</h2>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab.</div><a class=\\"button-3\\" href=\\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\\" style=\\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\\">To account</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Or copy the link into your browser window.</div><a class=\\"clink\\" href=\\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\\" style=\\"line-break: anywhere; margin-bottom: 40px;\\">https://gradido.net/contributions/own-contributions/1#contributionListItem-1</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Please do not reply to this email.</div>
<div class=\"content\" style=\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\">
<h2 style=\"margin-top: 15px; color: #383838;\">Contribution details</h2>
<div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab.</div><a class=\"button-3\" href=\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\" style=\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\">To account</a>
<div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">Or copy the link into your browser window.</div><a class=\"clink\" href=\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\" style=\"line-break: anywhere; margin-bottom: 40px;\">https://gradido.net/contributions/own-contributions/1#contributionListItem-1</a>
<div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">Please do not reply to this email.</div>
</div>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\">
<div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Kind regards,<br>your Gradido team
</p>
</div>
</div>
<footer>
<div class=\\"w-container footer_01\\">
<div class=\\"socialmedia\\" style=\\"display: flex; margin-top: 40px; max-width: 600px;\\"><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.facebook.com/groups/Gradido/\\" style=\\"width: 150px;\\"><img class=\\"bi-facebook\\" alt=\\"facebook\\" loading=\\"lazy\\" src=\\"cid:facebookicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://t.me/GradidoGruppe\\" style=\\"width: 150px;\\"><img class=\\"bi-telegram\\" alt=\\"Telegram\\" loading=\\"lazy\\" src=\\"cid:telegramicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://twitter.com/gradido\\" style=\\"width: 150px;\\"><img class=\\"bi-twitter\\" alt=\\"Twitter\\" loading=\\"lazy\\" src=\\"cid:twittericon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.youtube.com/c/GradidoNet\\" style=\\"width: 150px;\\"><img class=\\"bi-youtube\\" alt=\\"youtube\\" loading=\\"lazy\\" src=\\"cid:youtubeicon\\"></a></div>
<div class=\\"line\\" style=\\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\\"></div>
<div class=\\"footer\\" style=\\"padding-bottom: 20px;\\">
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">If you have any further questions, please contact our support.</div><a class=\\"footer_p2\\" href=\\"mailto:support@gradido.net\\" style=\\"color: #383838; font-weight: bold;\\">support@gradido.net</a>
<div> <img class=\\"image\\" alt=\\"Gradido Logo\\" src=\\"https://gdd.gradido.net/img/brand/green.png\\" style=\\"width: 200px; margin-top: 30px; margin-bottom: 30px;\\" width=\\"200\\"></div>
<div><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/impressum/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Impressum</a></div><br><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/datenschutz/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Privacy Policy</a>
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div>
<div class=\"w-container footer_01\">
<div class=\"socialmedia\" style=\"display: flex; margin-top: 40px; max-width: 600px;\"><a class=\"slink\" target=\"_blank\" href=\"https://www.facebook.com/groups/Gradido/\" style=\"width: 150px;\"><img class=\"bi-facebook\" alt=\"facebook\" loading=\"lazy\" src=\"cid:facebookicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://t.me/GradidoGruppe\" style=\"width: 150px;\"><img class=\"bi-telegram\" alt=\"Telegram\" loading=\"lazy\" src=\"cid:telegramicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://twitter.com/gradido\" style=\"width: 150px;\"><img class=\"bi-twitter\" alt=\"Twitter\" loading=\"lazy\" src=\"cid:twittericon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://www.youtube.com/c/GradidoNet\" style=\"width: 150px;\"><img class=\"bi-youtube\" alt=\"youtube\" loading=\"lazy\" src=\"cid:youtubeicon\"></a></div>
<div class=\"line\" style=\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\"></div>
<div class=\"footer\" style=\"padding-bottom: 20px;\">
<div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">If you have any further questions, please contact our support.</div><a class=\"footer_p2\" href=\"mailto:support@gradido.net\" style=\"color: #383838; font-weight: bold;\">support@gradido.net</a>
<div> <img class=\"image\" alt=\"Gradido Logo\" src=\"https://gdd.gradido.net/img/brand/green.png\" style=\"width: 200px; margin-top: 30px; margin-bottom: 30px;\" width=\"200\"></div>
<div><a class=\"terms_of_use\" href=\"https://gradido.net/de/impressum/\" target=\"_blank\" style=\"color: #9ca0a8;\">Impressum</a></div><br><a class=\"terms_of_use\" href=\"https://gradido.net/de/datenschutz/\" target=\"_blank\" style=\"color: #9ca0a8;\">Privacy Policy</a>
<div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div>
</div>
</div>
</footer>
@ -1042,12 +1042,12 @@ exports[`sendEmailVariants sendContributionDeletedEmail result has the correct h
exports[`sendEmailVariants sendContributionDeniedEmail result has the correct html as snapshot 1`] = `
"<!DOCTYPE html>
<html lang=\\"en\\">
<html lang=\"en\">
<head>
<meta content=\\"multipart/html; charset=UTF-8\\" http-equiv=\\"content-type\\">
<meta name=\\"viewport\\" content=\\"width=device-width, initial-scale=1\\">
<meta content=\"multipart/html; charset=UTF-8\" http-equiv=\"content-type\">
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">
<style>
.wf-force-outline-none[tabindex=\\"-1\\"]:focus {
.wf-force-outline-none[tabindex=\"-1\"]:focus {
outline: none;
}
</style>
@ -1173,37 +1173,37 @@ exports[`sendEmailVariants sendContributionDeniedEmail result has the correct ht
}
</style>
</head>
<body style=\\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\\">
<div class=\\"container\\" style=\\"max-width: 680px; margin: 0 auto; display: block;\\">
<body style=\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\">
<div class=\"container\" style=\"max-width: 680px; margin: 0 auto; display: block;\">
<header>
<div class=\\"head\\"><img class=\\"head-logo\\" alt=\\"Gradido Logo\\" loading=\\"lazy\\" src=\\"cid:gradidoheader\\" style=\\"width: 100%; height: auto;\\"></div>
<div class=\"head\"><img class=\"head-logo\" alt=\"Gradido Logo\" loading=\"lazy\" src=\"cid:gradidoheader\" style=\"width: 100%; height: auto;\"></div>
</header>
<div class=\\"wrapper\\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Your common good contribution was rejected</h2>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\">
<div class=\"wrapper\">
<h2 style=\"margin-top: 15px; color: #383838;\">Your common good contribution was rejected</h2>
<div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Hello Peter Lustig,</p>
<p>Your common good contribution “My contribution.” was rejected by Bibi Bloxberg.</p>
</div>
<div class=\\"content\\" style=\\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Contribution details</h2>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab.</div><a class=\\"button-3\\" href=\\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\\" style=\\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\\">To account</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Or copy the link into your browser window.</div><a class=\\"clink\\" href=\\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\\" style=\\"line-break: anywhere; margin-bottom: 40px;\\">https://gradido.net/contributions/own-contributions/1#contributionListItem-1</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Please do not reply to this email.</div>
<div class=\"content\" style=\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\">
<h2 style=\"margin-top: 15px; color: #383838;\">Contribution details</h2>
<div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab.</div><a class=\"button-3\" href=\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\" style=\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\">To account</a>
<div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">Or copy the link into your browser window.</div><a class=\"clink\" href=\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\" style=\"line-break: anywhere; margin-bottom: 40px;\">https://gradido.net/contributions/own-contributions/1#contributionListItem-1</a>
<div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">Please do not reply to this email.</div>
</div>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\">
<div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Kind regards,<br>your Gradido team
</p>
</div>
</div>
<footer>
<div class=\\"w-container footer_01\\">
<div class=\\"socialmedia\\" style=\\"display: flex; margin-top: 40px; max-width: 600px;\\"><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.facebook.com/groups/Gradido/\\" style=\\"width: 150px;\\"><img class=\\"bi-facebook\\" alt=\\"facebook\\" loading=\\"lazy\\" src=\\"cid:facebookicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://t.me/GradidoGruppe\\" style=\\"width: 150px;\\"><img class=\\"bi-telegram\\" alt=\\"Telegram\\" loading=\\"lazy\\" src=\\"cid:telegramicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://twitter.com/gradido\\" style=\\"width: 150px;\\"><img class=\\"bi-twitter\\" alt=\\"Twitter\\" loading=\\"lazy\\" src=\\"cid:twittericon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.youtube.com/c/GradidoNet\\" style=\\"width: 150px;\\"><img class=\\"bi-youtube\\" alt=\\"youtube\\" loading=\\"lazy\\" src=\\"cid:youtubeicon\\"></a></div>
<div class=\\"line\\" style=\\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\\"></div>
<div class=\\"footer\\" style=\\"padding-bottom: 20px;\\">
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">If you have any further questions, please contact our support.</div><a class=\\"footer_p2\\" href=\\"mailto:support@gradido.net\\" style=\\"color: #383838; font-weight: bold;\\">support@gradido.net</a>
<div> <img class=\\"image\\" alt=\\"Gradido Logo\\" src=\\"https://gdd.gradido.net/img/brand/green.png\\" style=\\"width: 200px; margin-top: 30px; margin-bottom: 30px;\\" width=\\"200\\"></div>
<div><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/impressum/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Impressum</a></div><br><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/datenschutz/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Privacy Policy</a>
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div>
<div class=\"w-container footer_01\">
<div class=\"socialmedia\" style=\"display: flex; margin-top: 40px; max-width: 600px;\"><a class=\"slink\" target=\"_blank\" href=\"https://www.facebook.com/groups/Gradido/\" style=\"width: 150px;\"><img class=\"bi-facebook\" alt=\"facebook\" loading=\"lazy\" src=\"cid:facebookicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://t.me/GradidoGruppe\" style=\"width: 150px;\"><img class=\"bi-telegram\" alt=\"Telegram\" loading=\"lazy\" src=\"cid:telegramicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://twitter.com/gradido\" style=\"width: 150px;\"><img class=\"bi-twitter\" alt=\"Twitter\" loading=\"lazy\" src=\"cid:twittericon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://www.youtube.com/c/GradidoNet\" style=\"width: 150px;\"><img class=\"bi-youtube\" alt=\"youtube\" loading=\"lazy\" src=\"cid:youtubeicon\"></a></div>
<div class=\"line\" style=\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\"></div>
<div class=\"footer\" style=\"padding-bottom: 20px;\">
<div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">If you have any further questions, please contact our support.</div><a class=\"footer_p2\" href=\"mailto:support@gradido.net\" style=\"color: #383838; font-weight: bold;\">support@gradido.net</a>
<div> <img class=\"image\" alt=\"Gradido Logo\" src=\"https://gdd.gradido.net/img/brand/green.png\" style=\"width: 200px; margin-top: 30px; margin-bottom: 30px;\" width=\"200\"></div>
<div><a class=\"terms_of_use\" href=\"https://gradido.net/de/impressum/\" target=\"_blank\" style=\"color: #9ca0a8;\">Impressum</a></div><br><a class=\"terms_of_use\" href=\"https://gradido.net/de/datenschutz/\" target=\"_blank\" style=\"color: #9ca0a8;\">Privacy Policy</a>
<div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div>
</div>
</div>
</footer>
@ -1214,12 +1214,12 @@ exports[`sendEmailVariants sendContributionDeniedEmail result has the correct ht
exports[`sendEmailVariants sendResetPasswordEmail result has the correct html as snapshot 1`] = `
"<!DOCTYPE html>
<html lang=\\"en\\">
<html lang=\"en\">
<head>
<meta content=\\"multipart/html; charset=UTF-8\\" http-equiv=\\"content-type\\">
<meta name=\\"viewport\\" content=\\"width=device-width, initial-scale=1\\">
<meta content=\"multipart/html; charset=UTF-8\" http-equiv=\"content-type\">
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">
<style>
.wf-force-outline-none[tabindex=\\"-1\\"]:focus {
.wf-force-outline-none[tabindex=\"-1\"]:focus {
outline: none;
}
</style>
@ -1345,41 +1345,41 @@ exports[`sendEmailVariants sendResetPasswordEmail result has the correct html as
}
</style>
</head>
<body style=\\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\\">
<div class=\\"container\\" style=\\"max-width: 680px; margin: 0 auto; display: block;\\">
<body style=\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\">
<div class=\"container\" style=\"max-width: 680px; margin: 0 auto; display: block;\">
<header>
<div class=\\"head\\"><img class=\\"head-logo\\" alt=\\"Gradido Logo\\" loading=\\"lazy\\" src=\\"cid:gradidoheader\\" style=\\"width: 100%; height: auto;\\"></div>
<div class=\"head\"><img class=\"head-logo\" alt=\"Gradido Logo\" loading=\"lazy\" src=\"cid:gradidoheader\" style=\"width: 100%; height: auto;\"></div>
</header>
<div class=\\"wrapper\\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Reset password</h2>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\">
<div class=\"wrapper\">
<h2 style=\"margin-top: 15px; color: #383838;\">Reset password</h2>
<div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Hello Peter Lustig,</p>
<p>You, or someone else, requested a password reset for this account.</p>
</div>
<div class=\\"content\\" style=\\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Reset password</h2>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">If it was you, please click here.</div><a class=\\"button-3\\" href=\\"http://localhost/reset-password/3762660021544901417\\" style=\\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\\">reset</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Or copy the link into your browser window.</div><a class=\\"clink\\" href=\\"http://localhost/reset-password/3762660021544901417\\" style=\\"line-break: anywhere; margin-bottom: 40px;\\">http://localhost/reset-password/3762660021544901417</a>
<div class=\"content\" style=\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\">
<h2 style=\"margin-top: 15px; color: #383838;\">Reset password</h2>
<div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">If it was you, please click here.</div><a class=\"button-3\" href=\"http://localhost/reset-password/3762660021544901417\" style=\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\">reset</a>
<div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">Or copy the link into your browser window.</div><a class=\"clink\" href=\"http://localhost/reset-password/3762660021544901417\" style=\"line-break: anywhere; margin-bottom: 40px;\">http://localhost/reset-password/3762660021544901417</a>
<requestnewlink>
<h2 style=\\"margin-top: 15px; color: #383838;\\">Request new valid link</h2>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">The link has a validity of 23 hours and 30 minutes.
If the validity of the link has already expired, you can have a new link sent to you here.</div><a class=\\"button-4\\" href=\\"http://localhost/forgot-password\\" style=\\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%; background-image: radial-gradient(circle farthest-corner at 0% 0%, #616161, #c2c2c2);\\">New link</a>
<h2 style=\"margin-top: 15px; color: #383838;\">Request new valid link</h2>
<div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">The link has a validity of 23 hours and 30 minutes.
If the validity of the link has already expired, you can have a new link sent to you here.</div><a class=\"button-4\" href=\"http://localhost/forgot-password\" style=\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%; background-image: radial-gradient(circle farthest-corner at 0% 0%, #616161, #c2c2c2);\">New link</a>
</requestnewlink>
</div>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\">
<div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Kind regards,<br>your Gradido team
</p>
</div>
</div>
<footer>
<div class=\\"w-container footer_01\\">
<div class=\\"socialmedia\\" style=\\"display: flex; margin-top: 40px; max-width: 600px;\\"><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.facebook.com/groups/Gradido/\\" style=\\"width: 150px;\\"><img class=\\"bi-facebook\\" alt=\\"facebook\\" loading=\\"lazy\\" src=\\"cid:facebookicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://t.me/GradidoGruppe\\" style=\\"width: 150px;\\"><img class=\\"bi-telegram\\" alt=\\"Telegram\\" loading=\\"lazy\\" src=\\"cid:telegramicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://twitter.com/gradido\\" style=\\"width: 150px;\\"><img class=\\"bi-twitter\\" alt=\\"Twitter\\" loading=\\"lazy\\" src=\\"cid:twittericon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.youtube.com/c/GradidoNet\\" style=\\"width: 150px;\\"><img class=\\"bi-youtube\\" alt=\\"youtube\\" loading=\\"lazy\\" src=\\"cid:youtubeicon\\"></a></div>
<div class=\\"line\\" style=\\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\\"></div>
<div class=\\"footer\\" style=\\"padding-bottom: 20px;\\">
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">If you have any further questions, please contact our support.</div><a class=\\"footer_p2\\" href=\\"mailto:support@gradido.net\\" style=\\"color: #383838; font-weight: bold;\\">support@gradido.net</a>
<div> <img class=\\"image\\" alt=\\"Gradido Logo\\" src=\\"https://gdd.gradido.net/img/brand/green.png\\" style=\\"width: 200px; margin-top: 30px; margin-bottom: 30px;\\" width=\\"200\\"></div>
<div><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/impressum/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Impressum</a></div><br><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/datenschutz/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Privacy Policy</a>
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div>
<div class=\"w-container footer_01\">
<div class=\"socialmedia\" style=\"display: flex; margin-top: 40px; max-width: 600px;\"><a class=\"slink\" target=\"_blank\" href=\"https://www.facebook.com/groups/Gradido/\" style=\"width: 150px;\"><img class=\"bi-facebook\" alt=\"facebook\" loading=\"lazy\" src=\"cid:facebookicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://t.me/GradidoGruppe\" style=\"width: 150px;\"><img class=\"bi-telegram\" alt=\"Telegram\" loading=\"lazy\" src=\"cid:telegramicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://twitter.com/gradido\" style=\"width: 150px;\"><img class=\"bi-twitter\" alt=\"Twitter\" loading=\"lazy\" src=\"cid:twittericon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://www.youtube.com/c/GradidoNet\" style=\"width: 150px;\"><img class=\"bi-youtube\" alt=\"youtube\" loading=\"lazy\" src=\"cid:youtubeicon\"></a></div>
<div class=\"line\" style=\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\"></div>
<div class=\"footer\" style=\"padding-bottom: 20px;\">
<div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">If you have any further questions, please contact our support.</div><a class=\"footer_p2\" href=\"mailto:support@gradido.net\" style=\"color: #383838; font-weight: bold;\">support@gradido.net</a>
<div> <img class=\"image\" alt=\"Gradido Logo\" src=\"https://gdd.gradido.net/img/brand/green.png\" style=\"width: 200px; margin-top: 30px; margin-bottom: 30px;\" width=\"200\"></div>
<div><a class=\"terms_of_use\" href=\"https://gradido.net/de/impressum/\" target=\"_blank\" style=\"color: #9ca0a8;\">Impressum</a></div><br><a class=\"terms_of_use\" href=\"https://gradido.net/de/datenschutz/\" target=\"_blank\" style=\"color: #9ca0a8;\">Privacy Policy</a>
<div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div>
</div>
</div>
</footer>
@ -1390,12 +1390,12 @@ If the validity of the link has already expired, you can have a new link sent to
exports[`sendEmailVariants sendTransactionLinkRedeemedEmail result has the correct html as snapshot 1`] = `
"<!DOCTYPE html>
<html lang=\\"en\\">
<html lang=\"en\">
<head>
<meta content=\\"multipart/html; charset=UTF-8\\" http-equiv=\\"content-type\\">
<meta name=\\"viewport\\" content=\\"width=device-width, initial-scale=1\\">
<meta content=\"multipart/html; charset=UTF-8\" http-equiv=\"content-type\">
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">
<style>
.wf-force-outline-none[tabindex=\\"-1\\"]:focus {
.wf-force-outline-none[tabindex=\"-1\"]:focus {
outline: none;
}
</style>
@ -1521,37 +1521,37 @@ exports[`sendEmailVariants sendTransactionLinkRedeemedEmail result has the corre
}
</style>
</head>
<body style=\\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\\">
<div class=\\"container\\" style=\\"max-width: 680px; margin: 0 auto; display: block;\\">
<body style=\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\">
<div class=\"container\" style=\"max-width: 680px; margin: 0 auto; display: block;\">
<header>
<div class=\\"head\\"><img class=\\"head-logo\\" alt=\\"Gradido Logo\\" loading=\\"lazy\\" src=\\"cid:gradidoheader\\" style=\\"width: 100%; height: auto;\\"></div>
<div class=\"head\"><img class=\"head-logo\" alt=\"Gradido Logo\" loading=\"lazy\" src=\"cid:gradidoheader\" style=\"width: 100%; height: auto;\"></div>
</header>
<div class=\\"wrapper\\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Bibi Bloxberg has redeemed your Gradido link</h2>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\">
<div class=\"wrapper\">
<h2 style=\"margin-top: 15px; color: #383838;\">Bibi Bloxberg has redeemed your Gradido link</h2>
<div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Hello Peter Lustig,</p>
<p>Bibi Bloxberg (bibi@bloxberg.de) has just redeemed your link.</p>
</div>
<div class=\\"content\\" style=\\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Transaction details</h2>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Amount: 17.65 GDD<br>Message: You deserve it! 🙏🏼<br>You can find transaction details in your Gradido account.
</div><a class=\\"button-3\\" href=\\"http://localhost/transactions\\" style=\\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\\">To account</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Please do not reply to this email.</div>
<div class=\"content\" style=\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\">
<h2 style=\"margin-top: 15px; color: #383838;\">Transaction details</h2>
<div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">Amount: 17.65 GDD<br>Message: You deserve it! 🙏🏼<br>You can find transaction details in your Gradido account.
</div><a class=\"button-3\" href=\"http://localhost/transactions\" style=\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\">To account</a>
<div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">Please do not reply to this email.</div>
</div>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\">
<div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Kind regards,<br>your Gradido team
</p>
</div>
</div>
<footer>
<div class=\\"w-container footer_01\\">
<div class=\\"socialmedia\\" style=\\"display: flex; margin-top: 40px; max-width: 600px;\\"><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.facebook.com/groups/Gradido/\\" style=\\"width: 150px;\\"><img class=\\"bi-facebook\\" alt=\\"facebook\\" loading=\\"lazy\\" src=\\"cid:facebookicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://t.me/GradidoGruppe\\" style=\\"width: 150px;\\"><img class=\\"bi-telegram\\" alt=\\"Telegram\\" loading=\\"lazy\\" src=\\"cid:telegramicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://twitter.com/gradido\\" style=\\"width: 150px;\\"><img class=\\"bi-twitter\\" alt=\\"Twitter\\" loading=\\"lazy\\" src=\\"cid:twittericon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.youtube.com/c/GradidoNet\\" style=\\"width: 150px;\\"><img class=\\"bi-youtube\\" alt=\\"youtube\\" loading=\\"lazy\\" src=\\"cid:youtubeicon\\"></a></div>
<div class=\\"line\\" style=\\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\\"></div>
<div class=\\"footer\\" style=\\"padding-bottom: 20px;\\">
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">If you have any further questions, please contact our support.</div><a class=\\"footer_p2\\" href=\\"mailto:support@gradido.net\\" style=\\"color: #383838; font-weight: bold;\\">support@gradido.net</a>
<div> <img class=\\"image\\" alt=\\"Gradido Logo\\" src=\\"https://gdd.gradido.net/img/brand/green.png\\" style=\\"width: 200px; margin-top: 30px; margin-bottom: 30px;\\" width=\\"200\\"></div>
<div><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/impressum/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Impressum</a></div><br><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/datenschutz/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Privacy Policy</a>
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div>
<div class=\"w-container footer_01\">
<div class=\"socialmedia\" style=\"display: flex; margin-top: 40px; max-width: 600px;\"><a class=\"slink\" target=\"_blank\" href=\"https://www.facebook.com/groups/Gradido/\" style=\"width: 150px;\"><img class=\"bi-facebook\" alt=\"facebook\" loading=\"lazy\" src=\"cid:facebookicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://t.me/GradidoGruppe\" style=\"width: 150px;\"><img class=\"bi-telegram\" alt=\"Telegram\" loading=\"lazy\" src=\"cid:telegramicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://twitter.com/gradido\" style=\"width: 150px;\"><img class=\"bi-twitter\" alt=\"Twitter\" loading=\"lazy\" src=\"cid:twittericon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://www.youtube.com/c/GradidoNet\" style=\"width: 150px;\"><img class=\"bi-youtube\" alt=\"youtube\" loading=\"lazy\" src=\"cid:youtubeicon\"></a></div>
<div class=\"line\" style=\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\"></div>
<div class=\"footer\" style=\"padding-bottom: 20px;\">
<div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">If you have any further questions, please contact our support.</div><a class=\"footer_p2\" href=\"mailto:support@gradido.net\" style=\"color: #383838; font-weight: bold;\">support@gradido.net</a>
<div> <img class=\"image\" alt=\"Gradido Logo\" src=\"https://gdd.gradido.net/img/brand/green.png\" style=\"width: 200px; margin-top: 30px; margin-bottom: 30px;\" width=\"200\"></div>
<div><a class=\"terms_of_use\" href=\"https://gradido.net/de/impressum/\" target=\"_blank\" style=\"color: #9ca0a8;\">Impressum</a></div><br><a class=\"terms_of_use\" href=\"https://gradido.net/de/datenschutz/\" target=\"_blank\" style=\"color: #9ca0a8;\">Privacy Policy</a>
<div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div>
</div>
</div>
</footer>
@ -1562,12 +1562,12 @@ exports[`sendEmailVariants sendTransactionLinkRedeemedEmail result has the corre
exports[`sendEmailVariants sendTransactionReceivedEmail result has the correct html as snapshot 1`] = `
"<!DOCTYPE html>
<html lang=\\"en\\">
<html lang=\"en\">
<head>
<meta content=\\"multipart/html; charset=UTF-8\\" http-equiv=\\"content-type\\">
<meta name=\\"viewport\\" content=\\"width=device-width, initial-scale=1\\">
<meta content=\"multipart/html; charset=UTF-8\" http-equiv=\"content-type\">
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">
<style>
.wf-force-outline-none[tabindex=\\"-1\\"]:focus {
.wf-force-outline-none[tabindex=\"-1\"]:focus {
outline: none;
}
</style>
@ -1693,40 +1693,40 @@ exports[`sendEmailVariants sendTransactionReceivedEmail result has the correct h
}
</style>
</head>
<body style=\\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\\">
<div class=\\"container\\" style=\\"max-width: 680px; margin: 0 auto; display: block;\\">
<body style=\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\">
<div class=\"container\" style=\"max-width: 680px; margin: 0 auto; display: block;\">
<header>
<div class=\\"head\\"><img class=\\"head-logo\\" alt=\\"Gradido Logo\\" loading=\\"lazy\\" src=\\"cid:gradidoheader\\" style=\\"width: 100%; height: auto;\\"></div>
<div class=\"head\"><img class=\"head-logo\" alt=\"Gradido Logo\" loading=\"lazy\" src=\"cid:gradidoheader\" style=\"width: 100%; height: auto;\"></div>
</header>
<div class=\\"wrapper\\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Bibi Bloxberg has sent you 37.40 Gradido</h2>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\">
<div class=\"wrapper\">
<h2 style=\"margin-top: 15px; color: #383838;\">Bibi Bloxberg has sent you 37.40 Gradido</h2>
<div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Hello Peter Lustig,</p>
<p> You have just received 37.40 GDD from Bibi Bloxberg (<a href=\\"mailto:bibi@bloxberg.de?subject=RE%3A%20Bibi%20Bloxberg%20has%20sent%20you%2037.40%20Gradido\\">bibi@bloxberg.de</a>).
<p> You have just received 37.40 GDD from Bibi Bloxberg (<a href=\"mailto:bibi@bloxberg.de?subject=RE%3A%20Bibi%20Bloxberg%20has%20sent%20you%2037.40%20Gradido\">bibi@bloxberg.de</a>).
</p>
</div>
<div class=\\"content\\" style=\\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Message</h2>
<div class=\\"child-left\\" style=\\"text-align: left;\\">
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Du bist schon lustiger ;)</div>
<div class=\"content\" style=\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\">
<h2 style=\"margin-top: 15px; color: #383838;\">Message</h2>
<div class=\"child-left\" style=\"text-align: left;\">
<div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">Du bist schon lustiger ;)</div>
</div>
<div class=\\"child-right\\" style=\\"text-align: right;\\"><a class=\\"button-5\\" href=\\"mailto:bibi@bloxberg.de?subject=RE%3A%20Bibi%20Bloxberg%20has%20sent%20you%2037.40%20Gradido\\" style=\\"display: inline-block; padding: 9px 15px; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); margin: 25px 0 25px 0; background: linear-gradient(135deg, #53900c, #6e6e6e); font-size: 20px; font-weight: 600; color: #f5f5f5; width: auto; box-shadow: 20px 20px 25px; transition: all 0.3s ease;\\"><span class=\\"chatbox-wrapper\\" style=\\"margin-right: 8px;\\"><img class=\\"bi-chatbox\\" alt=\\"chatbox\\" loading=\\"lazy\\" src=\\"cid:chatboxicon\\" style=\\"margin-bottom: -5px;\\"></span><span>Reply</span></a>
<div class=\"child-right\" style=\"text-align: right;\"><a class=\"button-5\" href=\"mailto:bibi@bloxberg.de?subject=RE%3A%20Bibi%20Bloxberg%20has%20sent%20you%2037.40%20Gradido\" style=\"display: inline-block; padding: 9px 15px; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); margin: 25px 0 25px 0; background: linear-gradient(135deg, #53900c, #6e6e6e); font-size: 20px; font-weight: 600; color: #f5f5f5; width: auto; box-shadow: 20px 20px 25px; transition: all 0.3s ease;\"><span class=\"chatbox-wrapper\" style=\"margin-right: 8px;\"><img class=\"bi-chatbox\" alt=\"chatbox\" loading=\"lazy\" src=\"cid:chatboxicon\" style=\"margin-bottom: -5px;\"></span><span>Reply</span></a>
</div>
</div><a class=\\"button-3\\" href=\\"http://localhost/transactions\\" style=\\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\\">To account</a>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\">
</div><a class=\"button-3\" href=\"http://localhost/transactions\" style=\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\">To account</a>
<div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Kind regards,<br>your Gradido team
</p>
</div>
</div>
<footer>
<div class=\\"w-container footer_01\\">
<div class=\\"socialmedia\\" style=\\"display: flex; margin-top: 40px; max-width: 600px;\\"><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.facebook.com/groups/Gradido/\\" style=\\"width: 150px;\\"><img class=\\"bi-facebook\\" alt=\\"facebook\\" loading=\\"lazy\\" src=\\"cid:facebookicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://t.me/GradidoGruppe\\" style=\\"width: 150px;\\"><img class=\\"bi-telegram\\" alt=\\"Telegram\\" loading=\\"lazy\\" src=\\"cid:telegramicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://twitter.com/gradido\\" style=\\"width: 150px;\\"><img class=\\"bi-twitter\\" alt=\\"Twitter\\" loading=\\"lazy\\" src=\\"cid:twittericon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.youtube.com/c/GradidoNet\\" style=\\"width: 150px;\\"><img class=\\"bi-youtube\\" alt=\\"youtube\\" loading=\\"lazy\\" src=\\"cid:youtubeicon\\"></a></div>
<div class=\\"line\\" style=\\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\\"></div>
<div class=\\"footer\\" style=\\"padding-bottom: 20px;\\">
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">If you have any further questions, please contact our support.</div><a class=\\"footer_p2\\" href=\\"mailto:support@gradido.net\\" style=\\"color: #383838; font-weight: bold;\\">support@gradido.net</a>
<div> <img class=\\"image\\" alt=\\"Gradido Logo\\" src=\\"https://gdd.gradido.net/img/brand/green.png\\" style=\\"width: 200px; margin-top: 30px; margin-bottom: 30px;\\" width=\\"200\\"></div>
<div><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/impressum/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Impressum</a></div><br><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/datenschutz/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Privacy Policy</a>
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div>
<div class=\"w-container footer_01\">
<div class=\"socialmedia\" style=\"display: flex; margin-top: 40px; max-width: 600px;\"><a class=\"slink\" target=\"_blank\" href=\"https://www.facebook.com/groups/Gradido/\" style=\"width: 150px;\"><img class=\"bi-facebook\" alt=\"facebook\" loading=\"lazy\" src=\"cid:facebookicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://t.me/GradidoGruppe\" style=\"width: 150px;\"><img class=\"bi-telegram\" alt=\"Telegram\" loading=\"lazy\" src=\"cid:telegramicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://twitter.com/gradido\" style=\"width: 150px;\"><img class=\"bi-twitter\" alt=\"Twitter\" loading=\"lazy\" src=\"cid:twittericon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://www.youtube.com/c/GradidoNet\" style=\"width: 150px;\"><img class=\"bi-youtube\" alt=\"youtube\" loading=\"lazy\" src=\"cid:youtubeicon\"></a></div>
<div class=\"line\" style=\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\"></div>
<div class=\"footer\" style=\"padding-bottom: 20px;\">
<div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">If you have any further questions, please contact our support.</div><a class=\"footer_p2\" href=\"mailto:support@gradido.net\" style=\"color: #383838; font-weight: bold;\">support@gradido.net</a>
<div> <img class=\"image\" alt=\"Gradido Logo\" src=\"https://gdd.gradido.net/img/brand/green.png\" style=\"width: 200px; margin-top: 30px; margin-bottom: 30px;\" width=\"200\"></div>
<div><a class=\"terms_of_use\" href=\"https://gradido.net/de/impressum/\" target=\"_blank\" style=\"color: #9ca0a8;\">Impressum</a></div><br><a class=\"terms_of_use\" href=\"https://gradido.net/de/datenschutz/\" target=\"_blank\" style=\"color: #9ca0a8;\">Privacy Policy</a>
<div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div>
</div>
</div>
</footer>

1
core/src/emails/index.ts Normal file
View File

@ -0,0 +1 @@
export * from './sendEmailVariants'

View File

@ -0,0 +1,7 @@
import { i18n } from './localization'
describe('localization', () => {
it('translate emails.accountMultiRegistration.contactSupport with Contact support', () => {
expect(i18n.__('emails.accountMultiRegistration.contactSupport')).toBe('Contact support')
})
})

View File

@ -0,0 +1,31 @@
import en from './locales/en.json'
import de from './locales/de.json'
import { I18n } from 'i18n'
function flatten(obj: any, prefix: string = ''): any {
const result: any = {}
for (const key in obj) {
if (typeof obj[key] === 'object' && obj[key] !== null) {
Object.assign(result, flatten(obj[key], prefix + key + '.'))
} else {
result[prefix + key] = obj[key]
}
}
return result
}
export const i18n = new I18n({
locales: ['en', 'de'],
defaultLocale: 'en',
staticCatalog: { en: flatten(en), de: flatten(de) },
api: {
__: 't', // now req.__ becomes req.t
__n: 'tn', // and req.__n can be called as req.tn
},
register: global,
mustacheConfig: {
tags: ['{', '}'],
disable: false,
},
})

View File

@ -1,12 +1,10 @@
import { createTransport } from 'nodemailer'
import { i18n } from '@test/testSetup'
import { CONFIG } from '@/config'
import { getLogger } from 'config-schema/test/testSetup'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { CONFIG } from '../config'
import { i18n } from './localization'
import { getLogger } from '../../../config-schema/test/testSetup.bun'
import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const'
import { sendEmailTranslated } from './sendEmailTranslated'
import { mock, jest, describe, it, expect, beforeEach, afterAll } from 'bun:test'
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.emails.sendEmailTranslated`)
@ -21,9 +19,8 @@ CONFIG.EMAIL_USERNAME = 'user'
CONFIG.EMAIL_PASSWORD = 'pwd'
CONFIG.EMAIL_TLS = true
jest.mock('nodemailer', () => {
mock.module('nodemailer', () => {
return {
__esModule: true,
createTransport: jest.fn(() => {
return {
sendMail: () => {
@ -36,6 +33,13 @@ jest.mock('nodemailer', () => {
}
})
afterAll(() => {
jest.restoreAllMocks()
})
const spySetLocale = jest.spyOn(i18n, 'setLocale')
const spyTranslate = jest.spyOn(i18n, '__')
describe('sendEmailTranslated', () => {
let result: Record<string, unknown> | boolean | null
@ -48,7 +52,7 @@ describe('sendEmailTranslated', () => {
},
template: 'accountMultiRegistration',
locals: {
locale: 'en',
language: 'en',
},
})
})
@ -72,7 +76,7 @@ describe('sendEmailTranslated', () => {
},
template: 'accountMultiRegistration',
locals: {
locale: 'en',
language: 'en',
},
})
})
@ -105,13 +109,13 @@ describe('sendEmailTranslated', () => {
})
})
})
it.skip('calls "i18n.setLocale" with "en"', () => {
expect(i18n.setLocale).toBeCalledWith('en')
it('calls "i18n.setLocale" with "en"', async () => {
expect(spySetLocale).toBeCalledWith('en')
})
it.skip('calls "i18n.__" for translation', () => {
expect(i18n.__).toBeCalled()
it('calls "i18n.__" for translation', () => {
expect(spyTranslate).toBeCalled()
})
})
@ -127,7 +131,7 @@ describe('sendEmailTranslated', () => {
},
template: 'accountMultiRegistration',
locals: {
locale: 'en',
language: 'en',
},
})
})

View File

@ -1,12 +1,16 @@
import path from 'path'
import Email from 'email-templates'
import i18n from 'i18n'
import { i18n } from './localization'
import { createTransport } from 'nodemailer'
import { CONFIG } from '@/config'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { CONFIG } from '../config'
import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const'
import { getLogger } from 'log4js'
import gradidoHeader from './templates/includes/gradido-header.jpeg'
import facebookIcon from './templates/includes/facebook-icon.png'
import telegramIcon from './templates/includes/telegram-icon.png'
import twitterIcon from './templates/includes/twitter-icon.png'
import youtubeIcon from './templates/includes/youtube-icon.png'
import chatboxIcon from './templates/includes/chatbox-icon.png'
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.emails.sendEmailTranslated`)
@ -21,7 +25,7 @@ export const sendEmailTranslated = async ({
}
template: string
locals: Record<string, unknown>
}): Promise<Record<string, unknown> | boolean | null> => {
}): Promise<Record<string, unknown> | boolean | null | Error> => {
// TODO: test the calling order of 'i18n.setLocale' for example: language of logging 'en', language of email receiver 'es', reset language of current user 'de'
if (!CONFIG.EMAIL) {
@ -31,7 +35,6 @@ export const sendEmailTranslated = async ({
// because language of receiver can differ from language of current user who triggers the sending
// const rememberLocaleToRestore = i18n.getLocale()
i18n.setLocale('en') // for logging
logger.info(
`send Email: language=${locals.locale as string} to=${receiver.to.substring(0, 3)}...` +
@ -45,6 +48,7 @@ export const sendEmailTranslated = async ({
)
receiver.to = CONFIG.EMAIL_TEST_RECEIVER
}
const transport = createTransport({
host: CONFIG.EMAIL_SMTP_HOST,
port: CONFIG.EMAIL_SMTP_PORT,
@ -56,8 +60,7 @@ export const sendEmailTranslated = async ({
},
})
i18n.setLocale(locals.locale as string) // for email
i18n.setLocale(locals.language as string) // for email
// TESTING: see 'README.md'
const email = new Email({
message: {
@ -66,9 +69,7 @@ export const sendEmailTranslated = async ({
send: CONFIG.EMAIL,
transport,
preview: false,
// i18n, // is only needed if you don't install i18n
})
const resultSend = await email
.send({
template: path.join(__dirname, 'templates', template),
@ -76,38 +77,39 @@ export const sendEmailTranslated = async ({
...receiver,
attachments: [
{
filename: 'gradido-header.jpeg',
path: path.join(__dirname, 'templates/includes/gradido-header.jpeg'),
// filename: 'gradido-header.jpeg',
content: gradidoHeader,
cid: 'gradidoheader',
},
{
filename: 'facebook-icon.png',
path: path.join(__dirname, 'templates/includes/facebook-icon.png'),
// filename: 'facebook-icon.png',
content: facebookIcon,
cid: 'facebookicon',
},
{
filename: 'telegram-icon.png',
path: path.join(__dirname, 'templates/includes/telegram-icon.png'),
// filename: 'telegram-icon.png',
content: telegramIcon,
cid: 'telegramicon',
},
{
filename: 'twitter-icon.png',
path: path.join(__dirname, 'templates/includes/twitter-icon.png'),
// filename: 'twitter-icon.png',
content: twitterIcon,
cid: 'twittericon',
},
{
filename: 'youtube-icon.png',
path: path.join(__dirname, 'templates/includes/youtube-icon.png'),
// filename: 'youtube-icon.png',
content: youtubeIcon,
cid: 'youtubeicon',
},
{
filename: 'chatbox-icon.png',
path: path.join(__dirname, 'templates/includes/chatbox-icon.png'),
// filename: 'chatbox-icon.png',
content: chatboxIcon,
cid: 'chatboxicon',
},
],
},
locals, // the 'locale' in here seems not to be used by 'email-template', because it doesn't work if the language isn't set before by 'i18n.setLocale'
// t: i18n.__.bind(i18n),
})
.catch((error: unknown) => {
logger.error('Error sending notification email', error)

View File

@ -1,12 +1,5 @@
import { ApolloServerTestClient } from 'apollo-server-testing'
import { Decimal } from 'decimal.js-light'
import { DataSource } from 'typeorm'
import { testEnvironment } from '@test/helpers'
import { i18n as localization } from '@test/testSetup'
import { getLogger } from 'config-schema/test/testSetup'
import { CONFIG } from '@/config'
import { CONFIG } from '../config'
import * as sendEmailTranslatedApi from './sendEmailTranslated'
import {
@ -26,6 +19,7 @@ const testMailServerHost = 'localhost'
const testMailServerPort = 1025
const testMailTLS = false
CONFIG.EMAIL = true
CONFIG.EMAIL_SENDER = 'info@gradido.net'
CONFIG.EMAIL_SMTP_HOST = testMailServerHost
CONFIG.EMAIL_SMTP_PORT = testMailServerPort
@ -46,22 +40,6 @@ jest.mock('nodemailer', () => {
}
})
let con: DataSource
let testEnv: {
mutate: ApolloServerTestClient['mutate']
query: ApolloServerTestClient['query']
con: DataSource
}
beforeAll(async () => {
testEnv = await testEnvironment(getLogger('apollo'), localization)
con = testEnv.con
})
afterAll(async () => {
await con.destroy()
})
const sendEmailTranslatedSpy = jest.spyOn(sendEmailTranslatedApi, 'sendEmailTranslated')
describe('sendEmailVariants', () => {
@ -81,7 +59,7 @@ describe('sendEmailVariants', () => {
contributionMemo: 'My contribution.',
contributionFrontendLink,
message: 'My message.',
})
})
})
describe('calls "sendEmailTranslated"', () => {
@ -91,24 +69,26 @@ describe('sendEmailVariants', () => {
to: 'Peter Lustig <peter@lustig.de>',
},
template: 'addedContributionMessage',
locals: {
locals: expect.objectContaining({
firstName: 'Peter',
lastName: 'Lustig',
locale: 'en',
language: 'en',
senderFirstName: 'Bibi',
senderLastName: 'Bloxberg',
contributionMemo: 'My contribution.',
contributionFrontendLink,
message: 'My message.',
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
},
}),
})
})
})
describe('result', () => {
it('is the expected object', () => {
expect(result).toMatchObject({
// bun testrunner bug, toMatchObject mess with 'result'
const resultClone = JSON.parse(JSON.stringify(result))
expect(resultClone).toMatchObject({
originalMessage: expect.objectContaining({
to: 'Peter Lustig <peter@lustig.de>',
from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>',
@ -145,23 +125,26 @@ describe('sendEmailVariants', () => {
to: 'Peter Lustig <peter@lustig.de>',
},
template: 'accountActivation',
locals: {
locals: expect.objectContaining({
firstName: 'Peter',
lastName: 'Lustig',
locale: 'en',
language: 'en',
activationLink: 'http://localhost/checkEmail/6627633878930542284',
timeDurationObject: { hours: 23, minutes: 30 },
resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL,
},
}),
})
})
})
describe('result', () => {
it('is the expected object', () => {
expect(result).toMatchObject({
// bun testrunner bug, toMatchObject mess with 'result'
const resultClone = JSON.parse(JSON.stringify(result))
expect(resultClone).toMatchObject({
originalMessage: expect.objectContaining({
to: 'Peter Lustig <peter@lustig.de>',
from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>',
@ -179,6 +162,8 @@ describe('sendEmailVariants', () => {
})
})
/*
describe('sendAccountMultiRegistrationEmail', () => {
beforeAll(async () => {
result = await sendAccountMultiRegistrationEmail({
@ -620,4 +605,5 @@ describe('sendEmailVariants', () => {
})
})
})
*/
})

View File

@ -0,0 +1,179 @@
import { Decimal } from 'decimal.js-light'
import { CONFIG } from '../config'
import { decimalSeparatorByLanguage } from '../util/utilities'
import { sendEmailTranslated } from './sendEmailTranslated'
export interface EmailCommonData {
firstName: string
lastName: string
email: string
language: string
}
export interface ContributionEmailCommonData {
senderFirstName: string
senderLastName: string
contributionMemo: string
contributionFrontendLink: string
}
function getEmailCommonLocales(): Record<string, unknown> {
return {
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD,
communityURL: CONFIG.COMMUNITY_URL,
}
}
export const sendAddedContributionMessageEmail = (
data: EmailCommonData & ContributionEmailCommonData & {
message: string
},
): Promise<Record<string, unknown> | boolean | null | Error> => {
return sendEmailTranslated({
receiver: {
to: `${data.firstName} ${data.lastName} <${data.email}>`,
},
template: 'addedContributionMessage',
locals: {
...data,
...getEmailCommonLocales(),
},
})
}
export const sendAccountActivationEmail = (data: EmailCommonData & {
activationLink: string
timeDurationObject: Record<string, unknown>
logoUrl?: string | null
}): Promise<Record<string, unknown> | boolean | null | Error> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'accountActivation',
locals: {
...data,
...getEmailCommonLocales(),
},
})
}
export const sendAccountMultiRegistrationEmail = (data: EmailCommonData): Promise<Record<string, unknown> | boolean | null | Error> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'accountMultiRegistration',
locals: {
...data,
...getEmailCommonLocales(),
},
})
}
export const sendContributionConfirmedEmail = (
data: EmailCommonData & ContributionEmailCommonData & {
contributionAmount: Decimal
},
): Promise<Record<string, unknown> | boolean | null | Error> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'contributionConfirmed',
locals: {
...data,
...getEmailCommonLocales(),
contributionAmount: decimalSeparatorByLanguage(data.contributionAmount, data.language),
},
})
}
export const sendContributionChangedByModeratorEmail = (
data: EmailCommonData & ContributionEmailCommonData & {
contributionMemoUpdated: string
},
): Promise<Record<string, unknown> | boolean | null | Error> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'contributionChangedByModerator',
locals: {
...data,
...getEmailCommonLocales(),
contributionMemoUpdated: data.contributionMemoUpdated,
},
})
}
export const sendContributionDeletedEmail = (
data: EmailCommonData & ContributionEmailCommonData,
): Promise<Record<string, unknown> | boolean | null | Error> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'contributionDeleted',
locals: {
...data,
...getEmailCommonLocales(),
},
})
}
export const sendContributionDeniedEmail = (
data: EmailCommonData & ContributionEmailCommonData,
): Promise<Record<string, unknown> | boolean | null | Error> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'contributionDenied',
locals: {
...data,
...getEmailCommonLocales(),
},
})
}
export const sendResetPasswordEmail = (data: EmailCommonData & {
resetLink: string
timeDurationObject: Record<string, unknown>
}): Promise<Record<string, unknown> | boolean | null | Error> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'resetPassword',
locals: {
...data,
...getEmailCommonLocales(),
},
})
}
export const sendTransactionLinkRedeemedEmail = (data: EmailCommonData & {
senderFirstName: string
senderLastName: string
senderEmail: string
transactionMemo: string
transactionAmount: Decimal
}): Promise<Record<string, unknown> | boolean | null | Error> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'transactionLinkRedeemed',
locals: {
...data,
transactionAmount: decimalSeparatorByLanguage(data.transactionAmount, data.language),
...getEmailCommonLocales(),
},
})
}
export const sendTransactionReceivedEmail = (data: EmailCommonData & {
senderFirstName: string
senderLastName: string
senderEmail: string
memo: string
transactionAmount: Decimal
}): Promise<Record<string, unknown> | boolean | null | Error> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'transactionReceived',
locals: {
...data,
transactionAmount: decimalSeparatorByLanguage(data.transactionAmount, data.language),
...getEmailCommonLocales(),
},
})
}

View File

Before

Width:  |  Height:  |  Size: 341 B

After

Width:  |  Height:  |  Size: 341 B

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 323 B

After

Width:  |  Height:  |  Size: 323 B

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Some files were not shown because too many files have changed in this diff Show More