diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index badb47e87..5eadf1e94 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -550,7 +550,7 @@ jobs: run: | cd e2e-tests/ yarn - yarn run cypress run --spec cypress/e2e/User.Authentication.feature,cypress/e2e/User.Authentication.ResetPassword.feature + yarn run cypress run --spec cypress/e2e/User.Authentication.feature,cypress/e2e/User.Authentication.ResetPassword.feature,cypress/e2e/User.Registration.feature - name: End-to-end tests | if tests failed, upload screenshots if: ${{ failure() && steps.e2e-tests.conclusion == 'failure' }} uses: actions/upload-artifact@v3 diff --git a/backend/src/emails/sendEmailTranslated.test.ts b/backend/src/emails/sendEmailTranslated.test.ts index eb4b26b92..79ba1cd77 100644 --- a/backend/src/emails/sendEmailTranslated.test.ts +++ b/backend/src/emails/sendEmailTranslated.test.ts @@ -8,6 +8,7 @@ CONFIG.EMAIL_SMTP_URL = 'EMAIL_SMTP_URL' CONFIG.EMAIL_SMTP_PORT = '1234' CONFIG.EMAIL_USERNAME = 'user' CONFIG.EMAIL_PASSWORD = 'pwd' +CONFIG.EMAIL_TLS = true jest.mock('nodemailer', () => { return { diff --git a/backend/src/emails/sendEmailVariants.test.ts b/backend/src/emails/sendEmailVariants.test.ts index 7e499feb9..9f30a8ce1 100644 --- a/backend/src/emails/sendEmailVariants.test.ts +++ b/backend/src/emails/sendEmailVariants.test.ts @@ -106,7 +106,7 @@ describe('sendEmailVariants', () => { 'you have received a message from Bibi Bloxberg regarding your common good contribution “My contribution.”.', ) expect(result.originalMessage.html).toContain( - 'To view and reply to the message, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!', + 'To view and reply to the message, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab!', ) expect(result.originalMessage.html).toContain( `Link to your account: ${CONFIG.EMAIL_LINK_OVERVIEW}`, @@ -424,7 +424,7 @@ describe('sendEmailVariants', () => { 'Your public good contribution “My contribution.” was rejected by Bibi Bloxberg.', ) expect(result.originalMessage.html).toContain( - 'To see your common good contributions and related messages, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!', + '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!', ) expect(result.originalMessage.html).toContain( `Link to your account: ${CONFIG.EMAIL_LINK_OVERVIEW}`, @@ -502,7 +502,7 @@ describe('sendEmailVariants', () => { 'Your public good contribution “My contribution.” was deleted by Bibi Bloxberg.', ) expect(result.originalMessage.html).toContain( - 'To see your common good contributions and related messages, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!', + '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!', ) expect(result.originalMessage.html).toContain( `Link to your account: ${CONFIG.EMAIL_LINK_OVERVIEW}`, diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index a8e36258c..c161f906f 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -24,7 +24,11 @@ import { listContributions, adminListAllContributions, } from '@/seeds/graphql/queries' -import { sendContributionConfirmedEmail } from '@/emails/sendEmailVariants' +import { + sendContributionConfirmedEmail, + sendContributionDeletedEmail, + sendContributionDeniedEmail, +} from '@/emails/sendEmailVariants' import { cleanDB, resetToken, @@ -50,21 +54,7 @@ import { ContributionListResult } from '@model/Contribution' import { ContributionStatus } from '@enum/ContributionStatus' import { Order } from '@enum/Order' -// mock account activation email to avoid console spam -jest.mock('@/emails/sendEmailVariants', () => { - const originalModule = jest.requireActual('@/emails/sendEmailVariants') - return { - __esModule: true, - ...originalModule, - // TODO: test the call of … - // sendAccountActivationEmail: jest.fn((a) => originalModule.sendAccountActivationEmail(a)), - sendContributionConfirmedEmail: jest.fn((a) => - originalModule.sendContributionConfirmedEmail(a), - ), - // TODO: test the call of … - // sendContributionRejectedEmail: jest.fn((a) => originalModule.sendContributionRejectedEmail(a)), - } -}) +jest.mock('@/emails/sendEmailVariants') let mutate: any, query: any, con: any let testEnv: any @@ -829,6 +819,18 @@ describe('ContributionResolver', () => { }), ) }) + + it('calls sendContributionDeniedEmail', async () => { + expect(sendContributionDeniedEmail).toBeCalledWith({ + firstName: 'Bibi', + lastName: 'Bloxberg', + email: 'bibi@bloxberg.de', + language: 'de', + senderFirstName: 'Peter', + senderLastName: 'Lustig', + contributionMemo: 'Test contribution to deny', + }) + }) }) }) }) @@ -2384,6 +2386,18 @@ describe('ContributionResolver', () => { }), ) }) + + it('calls sendContributionDeletedEmail', async () => { + expect(sendContributionDeletedEmail).toBeCalledWith({ + firstName: 'Peter', + lastName: 'Lustig', + email: 'peter@lustig.de', + language: 'de', + senderFirstName: 'Peter', + senderLastName: 'Lustig', + contributionMemo: 'Das war leider zu Viel!', + }) + }) }) describe('creation already confirmed', () => { @@ -2888,11 +2902,11 @@ describe('ContributionResolver', () => { state: 'PENDING', }), expect.objectContaining({ - amount: '200', - firstName: 'Bibi', + amount: '100', + firstName: 'Peter', id: expect.any(Number), - lastName: 'Bloxberg', - memo: 'Aktives Grundeinkommen', + lastName: 'Lustig', + memo: 'Test env contribution', messagesCount: 0, state: 'PENDING', }), diff --git a/backend/src/graphql/resolver/util/findContributions.ts b/backend/src/graphql/resolver/util/findContributions.ts index 0dc70cf30..84768d399 100644 --- a/backend/src/graphql/resolver/util/findContributions.ts +++ b/backend/src/graphql/resolver/util/findContributions.ts @@ -17,6 +17,7 @@ export const findContributions = async ( withDeleted: withDeleted, order: { createdAt: order, + id: order, }, relations: ['user'], skip: (currentPage - 1) * pageSize, diff --git a/backend/src/locales/de.json b/backend/src/locales/de.json index 530e8db10..4dc90def2 100644 --- a/backend/src/locales/de.json +++ b/backend/src/locales/de.json @@ -17,7 +17,7 @@ "addedContributionMessage": { "commonGoodContributionMessage": "du hast zu deinem Gemeinwohl-Beitrag „{contributionMemo}“ eine Nachricht von {senderFirstName} {senderLastName} erhalten.", "subject": "Gradido: Nachricht zu deinem Gemeinwohl-Beitrag", - "toSeeAndAnswerMessage": "Um die Nachricht zu sehen und darauf zu antworten, gehe in deinem Gradido-Konto ins Menü „Gemeinschaft“ auf den Tab „Meine Beiträge zum Gemeinwohl“!" + "toSeeAndAnswerMessage": "Um die Nachricht zu sehen und darauf zu antworten, gehe in deinem Gradido-Konto ins Menü „Schöpfen“ auf den Tab „Meine Beiträge“!" }, "contributionConfirmed": { "commonGoodContributionConfirmed": "dein Gemeinwohl-Beitrag „{contributionMemo}“ wurde soeben von {senderFirstName} {senderLastName} bestätigt und in deinem Gradido-Konto gutgeschrieben.", @@ -26,12 +26,12 @@ "contributionDeleted": { "commonGoodContributionDeleted": "dein Gemeinwohl-Beitrag „{contributionMemo}“ wurde von {senderFirstName} {senderLastName} gelöscht.", "subject": "Gradido: Dein Gemeinwohl-Beitrag wurde gelöscht", - "toSeeContributionsAndMessages": "Um deine Gemeinwohl-Beiträge und dazugehörige Nachrichten zu sehen, gehe in deinem Gradido-Konto ins Menü „Gemeinschaft“ auf den Tab „Meine Beiträge zum Gemeinwohl“!" + "toSeeContributionsAndMessages": "Um deine Gemeinwohl-Beiträge und dazugehörige Nachrichten zu sehen, gehe in deinem Gradido-Konto ins Menü „Schöpfen“ auf den Tab „Meine Beiträge“!" }, "contributionDenied": { "commonGoodContributionDenied": "dein Gemeinwohl-Beitrag „{contributionMemo}“ wurde von {senderFirstName} {senderLastName} abgelehnt.", "subject": "Gradido: Dein Gemeinwohl-Beitrag wurde abgelehnt", - "toSeeContributionsAndMessages": "Um deine Gemeinwohl-Beiträge und dazugehörige Nachrichten zu sehen, gehe in deinem Gradido-Konto ins Menü „Gemeinschaft“ auf den Tab „Meine Beiträge zum Gemeinwohl“!" + "toSeeContributionsAndMessages": "Um deine Gemeinwohl-Beiträge und dazugehörige Nachrichten zu sehen, gehe in deinem Gradido-Konto ins Menü „Schöpfen“ auf den Tab „Meine Beiträge“!" }, "general": { "amountGDD": "Betrag: {amountGDD} GDD", diff --git a/backend/src/locales/en.json b/backend/src/locales/en.json index 269c38629..74c5739bc 100644 --- a/backend/src/locales/en.json +++ b/backend/src/locales/en.json @@ -17,7 +17,7 @@ "addedContributionMessage": { "commonGoodContributionMessage": "you have received a message from {senderFirstName} {senderLastName} regarding your common good contribution “{contributionMemo}”.", "subject": "Gradido: Message about your common good contribution", - "toSeeAndAnswerMessage": "To view and reply to the message, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!" + "toSeeAndAnswerMessage": "To view and reply to the message, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab!" }, "contributionConfirmed": { "commonGoodContributionConfirmed": "Your public good contribution “{contributionMemo}” has just been confirmed by {senderFirstName} {senderLastName} and credited to your Gradido account.", @@ -26,12 +26,12 @@ "contributionDeleted": { "commonGoodContributionDeleted": "Your public good contribution “{contributionMemo}” was deleted by {senderFirstName} {senderLastName}.", "subject": "Gradido: Your common good contribution was deleted", - "toSeeContributionsAndMessages": "To see your common good contributions and related messages, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!" + "toSeeContributionsAndMessages": "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!" }, "contributionDenied": { "commonGoodContributionDenied": "Your public good contribution “{contributionMemo}” was rejected by {senderFirstName} {senderLastName}.", "subject": "Gradido: Your common good contribution was rejected", - "toSeeContributionsAndMessages": "To see your common good contributions and related messages, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!" + "toSeeContributionsAndMessages": "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!" }, "general": { "amountGDD": "Amount: {amountGDD} GDD", diff --git a/e2e-tests/cypress.config.ts b/e2e-tests/cypress.config.ts index 16ebb0e97..9dff98a9b 100644 --- a/e2e-tests/cypress.config.ts +++ b/e2e-tests/cypress.config.ts @@ -2,7 +2,7 @@ import { defineConfig } from 'cypress' import { addCucumberPreprocessorPlugin } from '@badeball/cypress-cucumber-preprocessor' import browserify from '@badeball/cypress-cucumber-preprocessor/browserify' -let resetPasswordLink: string +let emailLink: string async function setupNodeEvents( on: Cypress.PluginEvents, @@ -18,11 +18,11 @@ async function setupNodeEvents( ) on('task', { - setResetPasswordLink: (val) => { - return (resetPasswordLink = val) + setEmailLink: (link: string) => { + return (emailLink = link) }, - getResetPasswordLink: () => { - return resetPasswordLink + getEmailLink: () => { + return emailLink }, }) diff --git a/e2e-tests/cypress/e2e/User.Authentication.ResetPassword.feature b/e2e-tests/cypress/e2e/User.Authentication.ResetPassword.feature index 55ca87215..b0f660709 100644 --- a/e2e-tests/cypress/e2e/User.Authentication.ResetPassword.feature +++ b/e2e-tests/cypress/e2e/User.Authentication.ResetPassword.feature @@ -13,8 +13,8 @@ Feature: User Authentication - reset password And the user navigates to the forgot password page When the user enters the e-mail address "bibi@bloxberg.de" And the user submits the e-mail form - Then the user receives an e-mail containing the password reset link - When the user opens the password reset link in the browser + Then the user receives an e-mail containing the "password reset" link + When the user opens the "password reset" link in the browser And the user enters the password "12345Aa_" And the user repeats the password "12345Aa_" And the user submits the password form diff --git a/e2e-tests/cypress/e2e/User.Registration.feature b/e2e-tests/cypress/e2e/User.Registration.feature index ed53bb4b0..a50d57ea2 100644 --- a/e2e-tests/cypress/e2e/User.Registration.feature +++ b/e2e-tests/cypress/e2e/User.Registration.feature @@ -2,12 +2,16 @@ Feature: User registration As a user I want to register to create an account - @skip Scenario: Register successfully Given the user navigates to page "/register" When the user fills name and email "Regina" "Register" "regina@register.com" And the user agrees to the privacy policy And the user submits the registration form - Then the user can use a provided activation link - And the user can set a password "Aa12345_" - And the user can login with the credentials "regina@register.com" "Aa12345_" + Then the user receives an e-mail containing the "activation" link + When the user opens the "activation" link in the browser + And the user enters the password "12345Aa_" + And the user repeats the password "12345Aa_" + And the user submits the password form + And the user clicks the sign in button + Then the user submits the credentials "regina@register.com" "12345Aa_" + And the user is logged in with username "Regina Register" diff --git a/e2e-tests/cypress/e2e/models/RegistrationPage.ts b/e2e-tests/cypress/e2e/models/RegistrationPage.ts index 8cae26a26..2e9b48648 100644 --- a/e2e-tests/cypress/e2e/models/RegistrationPage.ts +++ b/e2e-tests/cypress/e2e/models/RegistrationPage.ts @@ -4,7 +4,7 @@ export class RegistrationPage { // selectors firstnameInput = '#registerFirstname' lastnameInput = '#registerLastname' - emailInput = '#Email-input-field' + emailInput = 'input[type=email]' checkbox = '#registerCheckbox' submitBtn = '[type=submit]' @@ -35,7 +35,7 @@ export class RegistrationPage { cy.get(this.checkbox).click({ force: true }) } - submitRegistrationPage() { + submitRegistrationForm() { cy.get(this.submitBtn).should('be.enabled') cy.get(this.submitBtn).click() } diff --git a/e2e-tests/cypress/e2e/models/ResetPasswordPage.ts b/e2e-tests/cypress/e2e/models/ResetPasswordPage.ts index 20134de6d..153b41a82 100644 --- a/e2e-tests/cypress/e2e/models/ResetPasswordPage.ts +++ b/e2e-tests/cypress/e2e/models/ResetPasswordPage.ts @@ -2,19 +2,19 @@ export class ResetPasswordPage { // selectors - newPasswordBlock = '#new-password-input-field' - newPasswordRepeatBlock = '#repeat-new-password-input-field' + newPasswordInput = '#new-password-input-field' + newPasswordRepeatInput = '#repeat-new-password-input-field' resetPasswordBtn = 'button[type=submit]' resetPasswordMessageBlock = '[data-test="reset-password-message"]' signinBtn = '.btn.test-message-button' enterNewPassword(password: string) { - cy.get(this.newPasswordBlock).find('input[type=password]').type(password) + cy.get(this.newPasswordInput).find('input[type=password]').type(password) return this } repeatNewPassword(password: string) { - cy.get(this.newPasswordRepeatBlock) + cy.get(this.newPasswordRepeatInput) .find('input[type=password]') .type(password) return this diff --git a/e2e-tests/cypress/support/step_definitions/email_steps.ts b/e2e-tests/cypress/support/step_definitions/email_steps.ts index b313442f2..d31e2474e 100644 --- a/e2e-tests/cypress/support/step_definitions/email_steps.ts +++ b/e2e-tests/cypress/support/step_definitions/email_steps.ts @@ -5,41 +5,55 @@ import { UserEMailSite } from '../../e2e/models/UserEMailSite' const userEMailSite = new UserEMailSite() const resetPasswordPage = new ResetPasswordPage() -Then('the user receives an e-mail containing the password reset link', () => { +Then('the user receives an e-mail containing the {string} link', (linkName: string) => { + let emailSubject: string + let linkPattern: RegExp + + switch (linkName) { + case 'activation': + emailSubject = 'Email Verification' + linkPattern = /\/checkEmail\/[0-9]+\d/ + break + case 'password reset': + emailSubject = 'asswor' + linkPattern = /\/reset-password\/[0-9]+\d/ + break + default: + throw new Error(`Error in "Then the user receives an e-mail containing the {string} link" step: incorrect linkname string "${linkName}"`) + } + cy.origin( Cypress.env('mailserverURL'), - { args: userEMailSite }, - (userEMailSite) => { - const linkPattern = /\/reset-password\/[0-9]+\d/ - - cy.visit('/') // navigate to user's e-maile site (on fake mail server) + { args: { emailSubject, linkPattern, userEMailSite } }, + ({ emailSubject, linkPattern, userEMailSite }) => { + cy.visit('/') // navigate to user's e-mail site (on fake mail server) cy.get(userEMailSite.emailInbox).should('be.visible') cy.get(userEMailSite.emailList) .find('.email-item') - .filter(':contains(asswor)') + .filter(`:contains(${emailSubject})`) .first() .click() cy.get(userEMailSite.emailMeta) .find(userEMailSite.emailSubject) - .contains('asswor') + .contains(emailSubject) - cy.get('.email-content') + cy.get('.email-content', { timeout: 2000}) .find('.plain-text') .contains(linkPattern) .invoke('text') .then((text) => { - const resetPasswordLink = text.match(linkPattern)[0] - cy.task('setResetPasswordLink', resetPasswordLink) + const emailLink = text.match(linkPattern)[0] + cy.task('setEmailLink', emailLink) }) } ) }) -When('the user opens the password reset link in the browser', () => { - cy.task('getResetPasswordLink').then((passwordResetLink) => { - cy.visit(passwordResetLink) +When('the user opens the {string} link in the browser', (linkName: string) => { + cy.task('getEmailLink').then((emailLink) => { + cy.visit(emailLink) }) - cy.get(resetPasswordPage.newPasswordRepeatBlock).should('be.visible') + cy.get(resetPasswordPage.newPasswordInput).should('be.visible') }) diff --git a/e2e-tests/cypress/support/step_definitions/user_registration_steps.ts b/e2e-tests/cypress/support/step_definitions/user_registration_steps.ts index 8f12338b0..e90956b6e 100644 --- a/e2e-tests/cypress/support/step_definitions/user_registration_steps.ts +++ b/e2e-tests/cypress/support/step_definitions/user_registration_steps.ts @@ -18,7 +18,7 @@ And('the user agrees to the privacy policy', () => { }) And('the user submits the registration form', () => { - registrationPage.submitRegistrationPage() + registrationPage.submitRegistrationForm() cy.get(registrationPage.RegistrationThanxHeadline).should('be.visible') cy.get(registrationPage.RegistrationThanxText).should('be.visible') }) diff --git a/e2e-tests/package.json b/e2e-tests/package.json index a6f817503..fc6403905 100644 --- a/e2e-tests/package.json +++ b/e2e-tests/package.json @@ -22,7 +22,7 @@ "@cypress/browserify-preprocessor": "^3.0.2", "@typescript-eslint/eslint-plugin": "^5.38.0", "@typescript-eslint/parser": "^5.38.0", - "cypress": "^10.4.0", + "cypress": "^12.7.0", "eslint": "^8.23.1", "eslint-config-prettier": "^8.3.0", "eslint-config-standard": "^16.0.3", diff --git a/frontend/public/img/svg/community.svg b/frontend/public/img/svg/community.svg new file mode 100644 index 000000000..8f1cdbd61 --- /dev/null +++ b/frontend/public/img/svg/community.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/public/img/svg/home.svg b/frontend/public/img/svg/home.svg new file mode 100644 index 000000000..9122b7e68 --- /dev/null +++ b/frontend/public/img/svg/home.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + diff --git a/frontend/public/img/svg/info.svg b/frontend/public/img/svg/info.svg new file mode 100644 index 000000000..1d1b88c65 --- /dev/null +++ b/frontend/public/img/svg/info.svg @@ -0,0 +1,182 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/public/img/svg/lines.png b/frontend/public/img/svg/lines.png new file mode 100644 index 000000000..d7bf781b4 Binary files /dev/null and b/frontend/public/img/svg/lines.png differ diff --git a/frontend/public/img/svg/logout.svg b/frontend/public/img/svg/logout.svg new file mode 100644 index 000000000..a9826a5dd --- /dev/null +++ b/frontend/public/img/svg/logout.svg @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/public/img/svg/send.svg b/frontend/public/img/svg/send.svg new file mode 100644 index 000000000..6f1d8dafa --- /dev/null +++ b/frontend/public/img/svg/send.svg @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/public/img/svg/settings.svg b/frontend/public/img/svg/settings.svg new file mode 100644 index 000000000..6966262f3 --- /dev/null +++ b/frontend/public/img/svg/settings.svg @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/public/img/svg/transaction.svg b/frontend/public/img/svg/transaction.svg new file mode 100644 index 000000000..f7cb156ff --- /dev/null +++ b/frontend/public/img/svg/transaction.svg @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/assets/scss/gradido-template.scss b/frontend/src/assets/scss/gradido-template.scss index 9edb72a4f..5a63d3e78 100644 --- a/frontend/src/assets/scss/gradido-template.scss +++ b/frontend/src/assets/scss/gradido-template.scss @@ -21,6 +21,10 @@ body { padding: 1px; } +.hover-font-bold:hover { + font-weight: bold; +} + .word-break { word-break: break-word; } diff --git a/frontend/src/components/ContributionMessages/ContributionMessagesListItem.vue b/frontend/src/components/ContributionMessages/ContributionMessagesListItem.vue index 6ae9d5933..01f835197 100644 --- a/frontend/src/components/ContributionMessages/ContributionMessagesListItem.vue +++ b/frontend/src/components/ContributionMessages/ContributionMessagesListItem.vue @@ -1,6 +1,23 @@