Skip to content

Commit

Permalink
Use tiles on membership confirmation page
Browse files Browse the repository at this point in the history
- Split general summary information and address section.
- Format address more nicely
- Add test for template logic in MembershipConfirmation

Ticket: https://phabricator.wikimedia.org/T326249
  • Loading branch information
gbirke committed Dec 20, 2024
1 parent 2f9589b commit 77efa5d
Show file tree
Hide file tree
Showing 6 changed files with 263 additions and 54 deletions.
131 changes: 104 additions & 27 deletions src/components/pages/MembershipConfirmation.vue
Original file line number Diff line number Diff line change
@@ -1,51 +1,128 @@
<template>
<div class="membership-confirmation">
<div class="donation-summary-wrapper has-background-bright columns has-padding-18">
<div class="column is-two-thirds">
<MembershipSummary
:address="confirmationData.address"
:membershipApplication="confirmationData.membershipApplication"
:salutations="salutations"
:address-is-invalid="false"
:countries="countries"
>
<template #title>
<h1>{{ $t( 'membership_confirmation_thanks_text' ) }}</h1>
</template>

<template #content>
<p v-if="hasIncentives">{{ $t( 'membership_confirmation_success_text_incentive' ) }}</p>
<p v-else>{{ $t( 'membership_confirmation_success_text' ) }}</p>
<p v-if="showBankTransferContent">{{ $t( 'membership_confirmation_success_text_bank_transfer' ) }}</p>
</template>
</MembershipSummary>
</div>

<div class="column is-one-third">
<SummaryLinks :confirmation-data="confirmationData"/>
</div>
<MembershipConfirmationBannerNotifier/>
<div class="membership-confirmation-summary membership-confirmation-card">
<h1>{{ $t( 'membership_confirmation_thanks_text' ) }}</h1>
<p v-html="$t( 'membership_confirmation_payment_data_text', summaryData )"/>

<p v-if="hasIncentives">{{ $t( 'membership_confirmation_success_text_incentive' ) }}</p>
<p v-else>{{ $t( 'membership_confirmation_success_text' ) }}</p>

<p v-if="showBankTransferContent">{{ $t( 'membership_confirmation_success_text_bank_transfer' ) }}</p>
</div>

<div class="membership-confirmation-card">
<h2>{{ $t( 'membership_confirmation_address_head' ) }}</h2>
<p>
<template v-if="address.applicantType === 'person'">{{ salutation }}{{ address.fullName }}</template>
<template v-else>{{ address.fullName }}</template>
<br />
{{ address.streetAddress }}<br />
{{ address.postalCode }} {{ address.city }}<br />
{{ countryName }}
</p>
<p>{{ address.email }}</p>
</div>

<MembershipConfirmationBannerNotifier/>
</div>
</template>

<script setup lang="ts">
import MembershipSummary from '@src/components/shared/MembershipSummary.vue';
import SummaryLinks from '@src/components/pages/membership_confirmation/SummaryLinks.vue';
import MembershipConfirmationBannerNotifier
from '@src/components/pages/membership_confirmation/MembershipConfirmationBannerNotifier.vue';
import { Salutation } from '@src/view_models/Salutation';
import { MembershipApplicationConfirmationData } from '@src/Domain/Membership/MembershipApplicationConfirmationData';
import { Country } from '@src/view_models/Country';
import { computed } from 'vue';
import { YearlyMembershipFee } from '@src/view_models/MembershipFee';
import { useI18n } from 'vue-i18n';
interface Props {
confirmationData: MembershipApplicationConfirmationData;
salutations: Salutation[];
countries: Country[];
}
const { t, n } = useI18n();
const props = defineProps<Props>();
const hasIncentives = props.confirmationData.membershipApplication.incentives?.length > 0;
const showBankTransferContent = props.confirmationData.membershipApplication.paymentType === 'UEB';
const geYearlyAmountForSmallIntervals = ( amount: number, interval: number, intervalTranslation: String ): string => {
if ( interval === 12 ) {
return '';
}
const formattedAmount = n( amount, { key: 'currency', currencyDisplay: 'name' } );
return `(${formattedAmount} ${intervalTranslation})`;
};
const address = props.confirmationData.address;
const salutation = computed( () => {
if ( !address.salutation ) {
return '';
}
const salutationObject = props.salutations.find( s => s.label === address.salutation );
if ( salutationObject === undefined ) {
return '';
}
return salutationObject?.display + ' ';
} );
const countryName = computed( () => {
const countryObject = props.countries.find( c => ( c.countryCode === address.countryCode ) );
return countryObject ? countryObject.countryFullName : '';
} );
const summaryData = computed( () => {
const membership = props.confirmationData.membershipApplication;
const yearlyFee = new YearlyMembershipFee(
membership.paymentIntervalInMonths,
membership.membershipFee
);
return {
paymentInterval: t( 'donation_form_payment_interval_' + membership.paymentIntervalInMonths ),
membershipType: t( membership.membershipType === 'active' ? 'membership_type_active' : 'membership_type_sustaining' ),
membershipFeeFormatted: n( yearlyFee.membershipFeePerInterval, { key: 'currency', currencyDisplay: 'name' } ),
membershipFeeYearlyFormatted: geYearlyAmountForSmallIntervals(
yearlyFee.yearlyFee,
yearlyFee.paymentIntervalInMonths,
t( 'donation_form_payment_interval_12' )
),
paymentType: t( membership.paymentType ),
};
} );
</script>

<style lang="scss">
@use '@src/scss/settings/colors';
@use '@src/scss/settings/breakpoints';
.membership-confirmation {
@include breakpoints.tablet-up {
display: grid;
grid-template-columns: 1fr 1fr;
grid-column-gap: 12px;
}
}
.membership-confirmation-summary {
@include breakpoints.tablet-up {
grid-column-start: 1;
grid-column-end: span 2;
}
}
.membership-confirmation-card {
background: #ffffff;
border: 1px solid colors.$gray-mid;
border-radius: 2px;
padding: 32px 16px;
line-height: 1.5;
margin-bottom: 12px;
}
</style>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<div>
<span>
<img src="https://de.wikipedia.org/wiki/Special:HideBanners?category=fr-thankyou&duration=15552000&reason=membership"
alt=""
width="0"
Expand All @@ -20,5 +20,5 @@
width="0"
height="0"
/>
</div>
</span>
</template>
2 changes: 1 addition & 1 deletion src/pages/membership_application_confirmation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ createVueApp( App,
assetsPath: pageData.assetsPath,
bucketClasses: bucketIdToCssClass( pageData.selectedBuckets ),
isFullWidth: true,
usesContentCards: false,
usesContentCards: true,
pageIdentifier: PAGE_IDENTIFIER,
page: MembershipConfirmation,
pageTitle: 'membership_application_confirmation_page_title',
Expand Down
26 changes: 26 additions & 0 deletions tests/data/salutations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Salutation } from '@src/view_models/Salutation';

export const salutations: Salutation[] = [
{
label: 'Herr',
value: 'Herr',
display: 'Herr',
greetings: {
formal: 'Good day',
informal: 'Yo!',
lastNameInformal: 'My Herr!',
},
},
{
label: 'Frau',
value: 'Frau',
display: 'Frau',
greetings: {
formal: 'Good day',
informal: 'Yo!',
lastNameInformal: 'My Frau!',
},
},
];

export default salutations;
129 changes: 129 additions & 0 deletions tests/unit/components/pages/MembershipConfirmation.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { mount } from '@vue/test-utils';
import { MembershipApplication } from '@src/Domain/Membership/MembershipApplication';
import { MembershipAddress } from '@src/Domain/Membership/MembershipAddress';
import { MembershipApplicationConfirmationData } from '@src/Domain/Membership/MembershipApplicationConfirmationData';
import salutations from '@test/data/salutations';
import countries from '@test/data/countries';
import MembershipConfirmation from '@src/components/pages/MembershipConfirmation.vue';

const privateAddress: MembershipAddress = {
applicantType: 'person',
city: 'Berlin',
countryCode: 'DE',
email: 'testperson@wikimedia.de',
fullName: 'Prof. Dr. Testy MacTest',
postalCode: '10963',
salutation: 'Herr',
streetAddress: 'Tempelhofer Ufer 26',
title: 'Prof. Dr.',
};

const companyAddress: MembershipAddress = {
applicantType: 'firma',
city: 'Company City',
countryCode: 'DE',
email: 'testcompany@wikimedia.de',
fullName: 'Test Company',
postalCode: '12345',
salutation: 'Firma',
streetAddress: 'Teststreet 123',
title: '',
};

const monthlyApplication: MembershipApplication = {
membershipFee: '15.00',
membershipType: 'sustaining',
paymentIntervalInMonths: 1,
paymentType: 'BEZ',
incentives: [],
};

const yearlyApplication: MembershipApplication = {
...monthlyApplication,
membershipFee: '199.00',
paymentIntervalInMonths: 12,
};

describe( 'MembershipConfirmation.vue', () => {
const getWrapper = ( membershipApplication: MembershipApplication, address: MembershipAddress ) => {
const confirmationData: MembershipApplicationConfirmationData = {
piwik: {
membershipApplicationConfirmationGoalId: 123,
},
membershipApplication,
address,
countries,
salutations,
};
return mount( MembershipConfirmation, {
props: {
confirmationData,
countries,
salutations,
},
global: {
mocks: {
$t: ( key: string, params?: Object ) => JSON.stringify( [ key, params ] ),
$n: ( amount: string ) => amount,
},
},
} );
};

test( 'displays the correct membership fee', () => {
const wrapper = getWrapper( yearlyApplication, privateAddress );
const summaryElement = wrapper.find( '.membership-confirmation-summary' );

expect( summaryElement.text() ).toContain( '199' );
expect( summaryElement.text() ).toContain( 'donation_form_payment_interval_12' );
expect( summaryElement.text() ).toContain( 'BEZ' );
expect( summaryElement.text() ).toContain( 'sustaining' );
} );

test( 'displays the calculated yearly membership fee', () => {
const wrapper = getWrapper( monthlyApplication, privateAddress );
const summaryElement = wrapper.find( '.membership-confirmation-summary' );

expect( summaryElement.text() ).toContain( '15' );
expect( summaryElement.text() ).toContain( '180' );
expect( summaryElement.text() ).toContain( 'donation_form_payment_interval_1' );
expect( summaryElement.text() ).toContain( 'donation_form_payment_interval_12' );
} );

test( 'displays the correct address for a private person', () => {
const wrapper = getWrapper( yearlyApplication, privateAddress );
const addressElement = wrapper.find( '.membership-confirmation-card:nth-child(2)' );

expect( addressElement.text() ).toContain( 'Prof. Dr. Testy MacTest' );
expect( addressElement.text() ).toContain( 'Tempelhofer Ufer 26' );
expect( addressElement.text() ).toContain( '10963 Berlin' );
expect( addressElement.text() ).toContain( 'Deutschland' );
expect( addressElement.text() ).toContain( 'testperson@wikimedia.de' );
} );

test( 'displays the correct address for a company', () => {
const wrapper = getWrapper( yearlyApplication, companyAddress );
const addressElement = wrapper.find( '.membership-confirmation-card:nth-child(2)' );

expect( addressElement.text() ).toContain( 'Test Company' );
expect( addressElement.text() ).toContain( 'Teststreet 123' );
expect( addressElement.text() ).toContain( '12345 Company City' );
expect( addressElement.text() ).toContain( 'Deutschland' );
expect( addressElement.text() ).toContain( 'testcompany@wikimedia.de' );
} );

test( 'displays different text when membership has incentives', () => {
const wrapperWithoutIncentives = getWrapper( yearlyApplication, privateAddress );
const wrapperWithIncentives = getWrapper( { ...yearlyApplication, incentives: [ 'incentive1', 'incentive2' ] }, privateAddress );

expect( wrapperWithoutIncentives.text() ).toContain( 'membership_confirmation_success_text' );
expect( wrapperWithIncentives.text() ).toContain( 'membership_confirmation_success_text_incentive' );
} );

test( 'displays additional text when payment is bank transfer', () => {
const wrapper = getWrapper( { ...yearlyApplication, paymentType: 'UEB' }, privateAddress );

expect( wrapper.text() ).toContain( 'membership_confirmation_success_text_bank_transfer' );
} );

} );
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { mount, VueWrapper } from '@vue/test-utils';
import MembershipSummary from '@src/components/shared/MembershipSummary.vue';
import { MembershipAddress } from '@src/Domain/Membership/MembershipAddress';
import { MembershipApplication } from '@src/Domain/Membership/MembershipApplication';
import { Salutation } from '@src/view_models/Salutation';
import salutations from '@test/data/salutations';
import countries from '@test/data/countries';

const privateAddress: MembershipAddress = {
Expand Down Expand Up @@ -49,29 +49,6 @@ const yearlyApplication: MembershipApplication = {
paymentIntervalInMonths: 12,
};

const salutations: Salutation[] = [
{
label: 'Herr',
value: 'Herr',
display: 'Herr',
greetings: {
formal: 'Good day',
informal: 'Yo!',
lastNameInformal: 'My Herr!',
},
},
{
label: 'Frau',
value: 'Frau',
display: 'Frau',
greetings: {
formal: 'Good day',
informal: 'Yo!',
lastNameInformal: 'My Frau!',
},
},
];

describe( 'MembershipSummary.vue', () => {

const getWrapper = ( address: MembershipAddress, membershipApplication: MembershipApplication, addressIsInvalid: boolean = false ): VueWrapper<any> => {
Expand Down

0 comments on commit 77efa5d

Please sign in to comment.